Is it possible to get a list of objects from a PySide2 slot (service call) via QML?

给你一囗甜甜゛ 提交于 2021-02-17 03:50:29

问题


I am trying to convert an existing PySide2/QtWidgets application to PySide2/QML. I am trying to get a list of custom objects from a Python service call via a QML MouseArea click.

I currently have a main script (main.py) that launches a QQuickView containing my QML (contained in main.qml). It also registers a custom type for my model (Role, defined in role.py) and exposes an instance of my service class (contained in mock_role_service.py) to the view's root context.

My QML displays correctly and I can click my MouseArea, but instead of getting back List[Role], I seem to be getting a QVariant tuple. More specifically, QVariant(PySide::PyObjectWrapper, ).

Relevant files:
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())
        }
    }
}
...

The result of the first console.log() call is QVariant(PySide::PyObjectWrapper, ) and the for loop is never entered.

I've only been able to find a couple of similar problems online, and the common solution to them so far (like in this answer) is to set the value to a property of the class and specify it to be of type QVariantList. This is because PySide2 apparently did away with their QVariant-like types, so I can't specify the slot's result type correctly.

However, I'm not sure this solution is appropriate for this situation because I am dealing with a service object. Setting a property on a service class to hold a return value feels brittle. Is there no other way of accomplishing this?


回答1:


Since you have not provided the Role class, I will assume that it is a QObject, if it is not, then you must modify your class so that it is not recognized by QML, besides only the signals, qproperties and slot are recognized in QML.

On the other hand qmlRegisterType is only necessary if you want to create objects in QML of the registered class, in your case I do not see it necessary.

Finally if you want to pass a list to QML you must use the signature 'QVariantList'(in PyQt if list is valid).

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);
            }
        }
    }
}

Output:

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


来源:https://stackoverflow.com/questions/57362530/is-it-possible-to-get-a-list-of-objects-from-a-pyside2-slot-service-call-via-q

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!