How Can I Update a Qml Object's Property from my Python file?

前端 未结 2 1858
轻奢々
轻奢々 2020-12-06 19:18

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 co

相关标签:
2条回答
  • 2020-12-06 19:33

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

    1. Pulling References from QML

    • Obtain the QML object through findChildren through another object.
    • Modify or access the property with setProperty() or property(), respectively or with QQmlProperty.

    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 QQmlProperty.

    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())
    

    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.

    2. Pushing References to QML

    • Create a QObject that has the same type of properties.
    • Export to QML using setContextProperty.
    • Make the binding between the properties of the QObject and the properties of the item.

    main.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"
        }
    }
    

    main.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())
    

    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.


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

    0 讨论(0)
  • 2020-12-06 19:38

    Try some thing like below (Not tested, but will give you an idea).

    create some objectname for rectangle as shown below:

    Rectangle {
            id: rectangle
            x: 187
            y: 92
            width: 200
            height: 200
            color: "blue"
            objectName: "myRect"
        }
    

    Interact with QML and find your child, then set the property.

        #INTERACT WITH QML
        engine = QQmlEngine()
        component = QQmlComponent(engine)
        component.loadUrl(QUrl('mainViewofHoomanApp.qml'))
        object = component.create()
    
        #FIND YOUR RECTANGLE AND SET WIDTH
        child = object.findChild(QObject,"myRect")
        child.setProperty("width", 500)  
    
    0 讨论(0)
提交回复
热议问题