是否可以通过 QML 从 PySide2 插槽(服务调用)获取对象列表?

Posted

技术标签:

【中文标题】是否可以通过 QML 从 PySide2 插槽(服务调用)获取对象列表?【英文标题】:Is it possible to get a list of objects from a PySide2 slot (service call) via QML? 【发布时间】:2019-08-05 16:23:49 【问题描述】:

我正在尝试将现有的 PySide2/QtWidgets 应用程序转换为 PySide2/QML。我正在尝试通过 QML MouseArea 点击从 Python 服务调用中获取自定义对象的列表。

我目前有一个主脚本 (main.py),它启动一个包含我的 QML 的 QQuickView(包含在 main.qml 中)。它还为我的模型注册了一个自定义类型(Role,在role.py 中定义)并将我的服务类的一个实例(包含在mock_role_service.py)公开给视图的根上下文。

我的 QML 显示正确,我可以单击我的 MouseArea,但我没有返回 List[Role],似乎得到了一个 QVariant 元组。更具体地说,QVariant(PySide::PyObjectWrapper, )

相关文件:mock_role_service.py

class MockRoleService(QObject):
    def __init__(self):
        super().__init__()

        self.next_id = 5
        self.records = 
            1: Role(id_=1,
                    name='Admin'),
            2: Role(id_=2,
                    name='User')
        

    @Slot(result=list)
    def find_all(self) -> List[Role]:
        return list(self.records.values())

main.py:

...
app = QGuiApplication(sys.argv)
qmlRegisterType(Role, 'Models', 1, 0, 'Role')

view = QQuickView()
url = QUrl('Views/main.qml')
view.setSource(url)
view.setResizeMode(QQuickView.SizeRootObjectToView)

role_service = MockRoleService()
view.rootContext().setContextProperty("roleService", role_service)

if view.status() == QQuickView.Error:
    sys.exit(-1)

view.show()

sys.exit(app.exec_())

main.qml

...
MouseArea 
    onClicked: 
        console.log(roleService.find_all())
        for (role in roleService.find_all()) 
            console.log(role.get_id())
        
    

...

第一次console.log() 调用的结果是QVariant(PySide::PyObjectWrapper, ),永远不会进入for循环。

我只能在网上找到几个类似的问题,到目前为止,它们的常见解决方案(如this 答案)是将值设置为类的属性并将其指定为QVariantList 类型。这是因为 PySide2 显然取消了它们的 QVariant 类类型,所以我无法正确指定插槽的结果类型。

但是,我不确定这个解决方案是否适合这种情况,因为我正在处理一个服务对象。在服务类上设置属性以保存返回值感觉很脆弱。有没有其他方法可以做到这一点?

【问题讨论】:

【参考方案1】:

由于你没有提供 Role 类,我假设它是一个 QObject,如果不是,那么你必须修改你的类,使其不被 QML 识别,除了信号,qproperties 和 slot 是在 QML 中被识别。

另一方面,qmlRegisterType 仅当您想在注册类的 QML 中创建对象时才需要,在您的情况下,我认为没有必要。

最后,如果您想将列表传递给 QML,您必须使用签名 'QVariantList'(如果列表有效,则在 PyQt 中)。

from typing import List
from PySide2.QtCore import Property, QObject, QUrl, Signal, Slot
from PySide2.QtGui import QGuiApplication
from PySide2.QtQml import qmlRegisterType
from PySide2.QtQuick import QQuickView


class Role(QObject):
    idChanged = Signal()
    nameChanged = Signal()

    def __init__(self, id_, name, parent=None):
        super().__init__(parent)

        self._id = id_
        self._name = name

    def get_id(self) -> int:
        return self._id

    def set_id(self, id_) -> None:
        if self._id != id_:
            self._id = id_
            self.idChanged.emit()

    def get_name(self) -> str:
        return self._name

    def set_name(self, name) -> None:
        if self._name != name:
            self._name = name
            self.nameChanged.emit()

    id_ = Property(int, fget=get_id, fset=set_id, notify=idChanged)
    name = Property(str, fget=get_name, fset=set_name, notify=nameChanged)


class MockRoleService(QObject):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.records = 
            1: Role(id_=1, name="Admin", parent=self),
            2: Role(id_=2, name="User", parent=self),
        

    @Slot(result="QVariantList")
    def find_all(self) -> List[Role]:
        return list(self.records.values())


if __name__ == "__main__":
    import os
    import sys

    app = QGuiApplication(sys.argv)
    # qmlRegisterType(Role, "Models", 1, 0, "Role")
    view = QQuickView()
    current_dir = os.path.dirname(os.path.realpath(__file__))
    url = QUrl.fromLocalFile(os.path.join(current_dir, "Views/main.qml"))
    view.setSource(url)
    view.setResizeMode(QQuickView.SizeRootObjectToView)

    role_service = MockRoleService()
    view.rootContext().setContextProperty("roleService", role_service)

    if view.status() == QQuickView.Error:
        sys.exit(-1)

    view.show()

    sys.exit(app.exec_())
import QtQuick 2.12

Item
    width: 640
    height: 480
    MouseArea 
        anchors.fill: parent
        onClicked: 
            var roles = roleService.find_all()
            console.log(roles)
            for (var i in roles) 
                var role = roles[i]
                console.log(role.id_, role.name);
            
        
    

输出:

qml: [Role(0x55b5385589b0),Role(0x55b538558d40)]
qml: 1 Admin
qml: 2 User

【讨论】:

这正是我需要的,谢谢!您认为 Role 是 QObject 的假设是正确的。为简洁起见,我省略了它,没有意识到这是让它发挥作用的重要部分。

以上是关于是否可以通过 QML 从 PySide2 插槽(服务调用)获取对象列表?的主要内容,如果未能解决你的问题,请参考以下文章

使用 PySide2 将 python 信号连接到 QML ui 插槽

PySide2 + QML:QApplication:通过了无效的样式覆盖,忽略它

PySide2/QML 填充和动画 Gridview 模型/委托

PySide2 QML - 如何在另一个 QML 文件中引用 QML 文件作为组件?

关于 QML 和 PySide2 的几个问题

如何使用 pyside2 插槽函数共享数据?