首页 > 解决方案 > 定义后将 Qt 属性附加到 Python 类;无法从 QML 访问

问题描述

我正在使用 PySide2 和 QML。当我QtCore.Property在 Python 对象中定义 a 时,它成功地为 QML 以及 Python 属性 get/set 方法设置了访问权限。但是,我有一种情况,在定义类之后附加属性会很有用,但这并没有像我预期的那样工作。

首先,一个有效的例子:

import sys
from PySide2.QtCore import QObject, Property, Signal
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine

class Person(QObject):
    def get_name(self):
        print('trying to access name')
        return self._name

    def set_name(self, new_name):
        print('trying to set name')
        self._name = new_name

    name = Property(str, get_name, set_name)
    
bob = Person()
bob.name = 'Bob'
print(f'Name: "{bob.name}"')
print(bob.__dict__)

app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty('backend', bob)
engine.load("example.qml")
sys.exit(app.exec_())

还有一个要查看的简单 QML 文件:

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    visible: true
    
    Component.onCompleted: { console.debug(JSON.stringify(backend)) }
    
    Text { text: backend.name }
}

生成的图形窗口显示“Bob”,控制台输出包括:

trying to set name
trying to access name
Name: "Bob"
{'_name': 'Bob'}
qml: {"objectName":"","name":"Bob"}

然而,看似简单的改变,事情就变得混乱了。我将 Python 类定义替换为:

class Person(QObject):
    pass
    
def get_name(obj):
    print('trying to access name')
    return obj._name

def set_name(obj, new_name):
    print('trying to set name')
    obj._name = new_name

Person.name = Property(str, get_name, set_name)

也就是说,getter、setter 和 Property 是在类定义之外定义的,并在事后附加到它上面。奇怪的是QML看不到Property,但是Python还是知道它的正确用法:

trying to set name
trying to access name
Name: "Bob"
{'_name': 'Bob'}
qml: {"objectName":""}
file:///Users/charles/Projects/qt/property-test/example.qml:9:9: Unable to assign [undefined] to QString

QtCore.Property对于如何正确设置 Python 属性,而不是 QML 属性,我摸不着头脑。我对它的实施有什么遗漏吗?

标签: pythonqtqmlpyside2

解决方案


TL; 博士;QProperties 不能在运行时定义。


QML 不使用 Python 自省来访问QObjects 属性,而是 Qt 在 中实现了自己的自省QMetaObject,可以在以下代码中看到:

class Person(QObject):
    def get_name(self):
        return self._name

    def set_name(self, new_name):
        self._name = new_name

    name = Property(str, get_name, set_name)


bob = Person()
bob.name = "Bob"

print("qproperties:")
print("============")
mo = bob.metaObject()
for i in range(mo.propertyOffset(), mo.propertyCount()):
    prop = mo.property(i)
    print(f"{i}: {prop.name()} = {prop.read(bob)}")

输出:

qproperties:
============
1: name = Bob
class Person(QObject):
    pass


def get_name(obj):
    return obj._name


def set_name(obj, new_name):
    obj._name = new_name


Person.name = Property(str, get_name, set_name)


bob = Person()
bob.name = "Bob"

print("qproperties:")
print("============")
mo = bob.metaObject()
for i in range(mo.propertyOffset(), mo.propertyCount()):
    prop = mo.property(i)
    print(f"{i}: {prop.name()} = {prop.read(bob)}")

输出:

qproperties:
============

PySide2(也叫PyQt5)在构建类时会创建QProperties,所以如果在类构建后添加QProperties,则不会添加QMetaObject。


另一种方法是使用QQmlPropertyMap

import sys
from PySide2.QtCore import QTimer
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine, QQmlPropertyMap

person_map = QQmlPropertyMap()
person_map.insert("name", "Bob")

app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
engine.rootContext().setContextProperty("backend", person_map)
engine.load("example.qml")

QTimer.singleShot(1000, lambda: person_map.setProperty("name", "Joe"))
sys.exit(app.exec_())

推荐阅读