Можно ли получить список объектов из слота PySide2 (сервисный вызов) через QML?

Я пытаюсь преобразовать существующее приложение PySide2 / QtWidgets в PySide2 / QML. Я пытаюсь получить список настраиваемых объектов из вызова службы Python с помощью MouseArea щелчка QML.

В настоящее время у меня есть основной скрипт (main.py), который запускает QQuickView, содержащий мой QML (содержащийся в 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 никогда не запускается.

Мне удалось найти только пару похожих проблем в Интернете и их общее решение (например, в этом ответ) заключается в том, чтобы установить значение свойства класса и указать его тип QVariantList. Это связано с тем, что PySide2 явно покончил с их QVariant-подобными типами, поэтому я не могу правильно указать тип результата слота.

Однако я не уверен, что это решение подходит для данной ситуации, потому что я имею дело с объектом службы. Установка свойства класса обслуживания для хранения возвращаемого значения кажется хрупкой. Нет другого способа добиться этого?


person Clifton Roberts    schedule 05.08.2019    source источник


Ответы (1)


Поскольку вы не предоставили класс Role, я предполагаю, что это QObject, если это не так, вы должны изменить свой класс так, чтобы он не распознавался QML, кроме того, в QML распознаются только сигналы, qproperties и слот .

С другой стороны, 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
person eyllanesc    schedule 06.08.2019
comment
Это именно то, что мне было нужно, спасибо! Ваше предположение, что роль была QObject, было правильным. Я опустил его для краткости, не понимая, что это важная часть работы. - person Clifton Roberts; 06.08.2019