如何从我的 Python 文件中更新 Qml 对象的属性?

2022-01-12 00:00:00 python pyqt pyqt5 qml

问题描述

我想在 Qml 中显示一个矩形,并且我想从我的 python 代码中更改矩形的属性(宽度、长度).其实python代码中有一个socket连接,通过它从另一台电脑接收width和length的值.简单地说:另一个用户应该能够实时调整这个矩形.我知道如何在我的 python 文件中建立套接字连接并使用 PyQt5,我可以从 python 显示 qml 文件.

I want to show a rectangle in Qml and I want to change the rectangle's properties(width, length) from my python code. In fact, there is a socket connection in the python code, through which the values of width and length are received from another computer. To put it simple: another user should be able to adjust this rectangle in real-time. I know how to make a socket connection in my python file and using PyQt5, I can show the qml file from python.

但是,我无法通过我的 python 代码访问矩形的参数.我该怎么做?

However, I am in trouble to access the rectangle's parameters through my python code. How can I do that?

这是我的 qml 文件的简化示例:

This is a simplified sample of my qml file:

import QtQuick 2.11
import QtQuick.Window 2.2
import QtQuick.Controls 2.2

ApplicationWindow {    
    visible: true
    width: Screen.width/2
    height: Screen.height/2
    Rectangle {
        id: rectangle
        x: 187
        y: 92
        width: 200
        height: 200
        color: "blue"
    }
}

这是我在 .py 文件中写的内容:

And here is what I have written in my .py file:

from PyQt5.QtQml import QQmlApplicationEngine, QQmlProperty
from PyQt5.QtQuick import QQuickWindow, QQuickView
from PyQt5.QtCore import QObject, QUrl
from PyQt5.QtWidgets import QApplication
import sys
def run():
    myApp = QApplication(sys.argv)
    myEngine = QQmlApplicationEngine()

    myEngine.load('mainViewofHoomanApp.qml')


    if not myEngine.rootObjects():
        return -1
    return myApp.exec_()

if __name__ == "__main__":
    sys.exit(run())


解决方案

在python/C++中有几种方法可以修改QML元素的属性,各有优缺点.

There are several methods to modify a property of a QML element from python/C++, and each has its advantages and disadvantages.

  • 通过另一个对象通过findChildren获取QML对象.
  • 分别使用 setProperty()property() 或使用 QQmlProperty 修改或访问属性.
  • Obtain the QML object through findChildren through another object.
  • Modify or access the property with setProperty() or property(), respectively or with QQmlProperty.

ma​​in.qml(qml 用于下一个 2 .py)

main.qml (the qml is for the next 2 .py)

import QtQuick 2.11
import QtQuick.Window 2.2
import QtQuick.Controls 2.2

ApplicationWindow {    
    visible: true
    width: Screen.width/2
    height: Screen.height/2
    Rectangle {
        id: rectangle
        x: 187
        y: 92
        width: 200
        height: 200
        color: "blue"
        objectName: "foo_object"
    }
}

1.1 setProperty(), property().

import os
import sys
from PyQt5 import QtCore, QtGui, QtQml
from functools import partial

def testing(r):
    import random
    w = r.property("width")
    h = r.property("height")
    print("width: {}, height: {}".format(w, h))
    r.setProperty("width", random.randint(100, 400))
    r.setProperty("height", random.randint(100, 400))

def run():
    myApp = QtGui.QGuiApplication(sys.argv)
    myEngine = QtQml.QQmlApplicationEngine()
    directory = os.path.dirname(os.path.abspath(__file__))
    myEngine.load(QtCore.QUrl.fromLocalFile(os.path.join(directory, 'main.qml')))
    if not myEngine.rootObjects():
        return -1
    r = myEngine.rootObjects()[0].findChild(QtCore.QObject, "foo_object")
    timer = QtCore.QTimer(interval=500)
    timer.timeout.connect(partial(testing, r))
    timer.start()
    return myApp.exec_()

if __name__ == "__main__":
    sys.exit(run())

1.2 QQml 属性.

import os
import sys
from PyQt5 import QtCore, QtGui, QtQml
from functools import partial

def testing(r):
    import random
    w_prop = QtQml.QQmlProperty(r, "width")
    h_prop = QtQml.QQmlProperty(r, "height")
    print("width: {}, height: {}".format(w_prop.read(), w_prop.read()))
    w_prop.write(random.randint(100, 400))
    h_prop.write(random.randint(100, 400))

def run():
    myApp = QtGui.QGuiApplication(sys.argv)
    myEngine = QtQml.QQmlApplicationEngine()
    directory = os.path.dirname(os.path.abspath(__file__))
    myEngine.load(QtCore.QUrl.fromLocalFile(os.path.join(directory, 'main.qml')))

    if not myEngine.rootObjects():
        return -1
    r = myEngine.rootObjects()[0].findChild(QtCore.QObject, "foo_object")
    timer = QtCore.QTimer(interval=500)
    timer.timeout.connect(partial(testing, r))
    timer.start()
    return myApp.exec_()

if __name__ == "__main__":
    sys.exit(run())

这种方法的一个缺点是如果对象与根对象的关系很复杂(有时其他 QML 中的对象很难用 findChild 访问)访问对象的部分变得复杂,有时甚至不可能,所以这种方法将失败.另一个问题是,当使用 objectName 作为主要搜索数据时,Python 层对 QML 层的依赖性很高,因为如果在 QML 中修改了 objectName,则必须修改 python 中的逻辑.另一个缺点是,如果不管理 QML 对象的生命周期,它可能会在 Python 不知情的情况下被消除,因此它会访问不正确的引用,从而导致应用程序意外终止.

A disadvantage of this method is that if the relation of the object with the rootobject is complex(Sometimes objects that are in other QMLs are hard to access with findChild) the part of accessing the object becomes complicated and sometimes impossible so this method will fail. Another problem is that when using the objectName as the main search data there is a high dependency of the Python layer to the QML layer since if the objectName is modified in QML the logic in python would have to be modified. Another disadvantage is that by not managing the life cycle of the QML object it could be eliminated without Python knowing so it would access an incorrect reference causing the application to terminate unexpectedly.

  • 创建一个具有相同类型属性的 QObject.
  • 使用 setContextProperty 导出到 QML.
  • 在 QObject 的属性和 item 的属性之间进行绑定.

ma​​in.qml

import QtQuick 2.11
import QtQuick.Window 2.2
import QtQuick.Controls 2.2

ApplicationWindow {    
    visible: true
    width: Screen.width/2
    height: Screen.height/2
    Rectangle {
        id: rectangle
        x: 187
        y: 92
        width: r_manager.width
        height: r_manager.height
        color: "blue"
    }
}

ma​​in.py

import os
import sys
from PyQt5 import QtCore, QtGui, QtQml
from functools import partial

class RectangleManager(QtCore.QObject):
    widthChanged = QtCore.pyqtSignal(float)
    heightChanged = QtCore.pyqtSignal(float)

    def __init__(self, parent=None):
        super(RectangleManager, self).__init__(parent)
        self._width = 100
        self._height = 100

    @QtCore.pyqtProperty(float, notify=widthChanged)
    def width(self):
        return self._width

    @width.setter
    def width(self, w):
        if self._width != w:
            self._width = w
            self.widthChanged.emit(w)

    @QtCore.pyqtProperty(float, notify=heightChanged)
    def height(self):
        return self._height

    @height.setter
    def height(self, h):
        if self._height != h:
            self._height = h
            self.heightChanged.emit(h)

def testing(r):
    import random
    print("width: {}, height: {}".format(r.width, r.height))
    r.width = random.randint(100, 400)
    r.height = random.randint(100, 400)

def run():
    myApp = QtGui.QGuiApplication(sys.argv)
    myEngine = QtQml.QQmlApplicationEngine()
    manager = RectangleManager()
    myEngine.rootContext().setContextProperty("r_manager", manager)
    directory = os.path.dirname(os.path.abspath(__file__))
    myEngine.load(QtCore.QUrl.fromLocalFile(os.path.join(directory, 'main.qml')))

    if not myEngine.rootObjects():
        return -1
    timer = QtCore.QTimer(interval=500)
    timer.timeout.connect(partial(testing, manager))
    timer.start()
    return myApp.exec_()

if __name__ == "__main__":
    sys.exit(run())

缺点是您必须编写更多代码.优点是对象可以被所有 QML 访问,因为它使用 setContextProperty,另一个优点是如果 QML 对象被删除,它不会产生问题,因为只消除了绑定.最后,通过不使用 objectName,依赖项不存在.

The disadvantage is that you have to write some more code. The advantage is that the object is accessible by all the QML since it uses setContextProperty, another advantage is that if the QML object is deleted it does not generate problems since only the binding is eliminated. And finally, by not using the objectName, the dependency does not exist.

所以我更喜欢使用第二种方法,更多信息请阅读 从 C++ 与 QML 交互.

So I prefer to use the second method, for more information read Interacting with QML from C++.

相关文章