如何在 PySide 中模块化创建属性
Posted
技术标签:
【中文标题】如何在 PySide 中模块化创建属性【英文标题】:How to modularize property creation in PySide 【发布时间】:2020-04-20 08:45:13 【问题描述】:我在很大程度上遵循here 使用属性的说明,我可以只使用那里给出的 Person 对象作为后端,但这不是很有用。我想弄清楚如何做以下两件事:
-
在后端使用多个此类类的多个实例,并以 PySide/QML 不会抱怨的方式连接它们
允许在运行时确定通过模块自定义后端(即,我最终想要组件化应用程序 - 有不同的组件实现接口,组件分别对 GUI 和后端做出贡献;但这个问题只涉及后端)
这与在主后端类上简单地定义所有这些属性以及它们的 setter 和 getter(我能够做到)形成对比,这就是我在问题中模块化的意思。
我修改了链接中的 Person 示例,使其成为 UI 可以更改的内容,并为它提供了一个额外的属性以用于踢球...
person.py
from PySide2.QtCore import QObject, Signal, Property
class Person(QObject):
def __init__(self, name, age):
QObject.__init__(self)
self._name = name
self._age = age
def getName(self):
return self._name
def setName(self, name):
print(f"Setting name to name")
self._name = name
def getAge(self):
return self._age
def setAge(self, age):
print(f"Setting age to age")
self._age = age
@Signal
def name_changed(self):
pass
@Signal
def age_changed(self):
pass
name = Property(str, getName, setName, notify=name_changed)
age = Property(str, getAge, setAge, notify=age_changed)
作为示例,我将创建两个 Person 实例。我作为类成员创建的第一个实例。这不是我真正想要的,但更类似于链接中使用属性的方式。第二个实例是我真正想要的,它的属性是实例成员,这样我就可以在运行时从应用程序的其他地方添加它们。目前这两种方法都不起作用
main.py
import sys
from os.path import abspath, dirname, join
from PySide2.QtCore import QObject, Property, Signal
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import QQmlApplicationEngine
from person import Person
class Backend(QObject):
def __init__(self):
QObject.__init__(self)
def registerProperty(self, name : str, prop):
setattr(self, name, prop)
person1 = Person("Jill", 29)
if __name__ == '__main__':
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
context = engine.rootContext()
# Instance of the Python object
backend = Backend()
# simulate properties added by another module
backend.registerProperty("person2", Person("Jack", 30))
qmlFile = join(dirname(__file__), 'view3.qml')
engine.load(abspath(qmlFile))
# Expose the Python object to QML
context.setContextProperty("backend", backend)
# I tried this but it did nothing
# context.setContextProperty("backend.jack", backend.jack)
# context.setContextProperty("backend.jill", backend.jill)
if not engine.rootObjects():
sys.exit(-1)
sys.exit(app.exec_())
终于view3.qml文件很简单
import QtQuick 2.0
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import QtQuick.Window 2.12
ApplicationWindow
visible: true
ColumnLayout
TextField
implicitWidth: 200
onAccepted:
backend.person1.name = text
TextField
implicitWidth: 200
onAccepted:
backend.person1.age = text
TextField
implicitWidth: 200
onAccepted:
backend.person2.name = text
TextField
implicitWidth: 200
onAccepted:
backend.person2.age = text
当我尝试在 UI 中设置任何值时,错误总是相同的(错误出现在 QML 文件中)
TypeError: Value is undefined and could not be converted to an object
最终,我希望将此类对象嵌套到任意深度。有没有办法实现我在这里尝试做的事情?或者我可能完全偏离了我正在设置的方式?
【问题讨论】:
【参考方案1】:我不知道我是否有资格就 GUI 应用程序的整体架构设计向您提供建议,但我想我可以解释发生了什么问题,并建议一种方法来执行您所描述的操作。您的 registerProperty
方法添加了一个 Python 属性,但正如您所见,这不会使其在 QML 中可见。
坏消息: Qt 属性在创建后无法添加到对象。好消息: 您可以创建一个 Qt 属性,它是一个列表 (或字典),然后添加到其中。
需要注意的一个陷阱是,要将列表公开给 QML,您将其类型指定为 'QVariantList'
。 (对于字典,请使用'QVariantMap'
,并确保您的键是字符串。)
下面是您的 Backend 类的外观示例。 (使用super()
访问父类意味着您不必将self
传递给它的初始化程序。)
from Pyside2.QtCore import QObject, Property, Signal
class Backend(QObject):
def __init__(self):
super().__init__()
self.people = []
people_changed = Signal('QVariantList')
@Property('QVariantList', notify=people_changed)
def people(self):
return self._people
@value.setter
def people(self, new_people):
self._people = new_people
self.people_changed.emit(new_people)
def add_person(self, name, age):
self.people.append(Person(name, age, self))
# Note: only ASSIGNING will automatically fire the changed
# signal. If you append, you have to fire it yourself.
self.people_changed.emit(self.people)
这将使 QML 在您添加人员时保持最新;您还可以创建类似的方法来删除人员。将每个人作为 Backend 对象的父级确保只要您的 Backend 仍然存在,Qt 就会保留对他们的引用。
对于真正动态的属性集合,也许您可以为您的***后端对象提供一个字典,您的其他组件将添加到该字典中。所以backend.people
会变成backend.properties['people']
,一个特定的模块会负责将该键添加到properties
字典中,然后在其中添加和删除条目。
指定所有这些 getter 和 setter 很麻烦,不是吗?我花了很长时间觉得肯定有更好的方法,直到最近才在 Stack Overflow 上遇到了一个解决方案。使用this metaclass,您的 Person 类和我在上面发布的 Backend 可以简化为:
from PySide2.QtCore import QObject
# Assuming you save the linked code as properties.py:
from properties import PropertyMeta, Property
class Person(QObject, metaclass=PropertyMeta):
name = Property(str)
age = Property(int)
def __init__(self, name, age, parent=None):
super().__init__(parent)
self.name = name
self.age = age
class Backend(QObject, metaclass=PropertyMeta):
people = Property(list)
def __init__(self):
super().__init__()
self.people = []
def add_person(self, name, age):
self.people.append(Person(name, age, self))
# Automatically emits changed signal, even for in-place changes
(我也将age
更改为int
,但如果需要,它仍然可以是str
。)
【讨论】:
以上是关于如何在 PySide 中模块化创建属性的主要内容,如果未能解决你的问题,请参考以下文章
如何在PySide / PyQt中连接QApplication()和QWidget()对象?
在 PySide 的 QTreeView 中更改时如何获取项目的先前名称