Элемент списка доступа в QML из python с использованием QAbstractListModel

Я новичок в Qt, и я создаю приложение:

  1. Использование QML для дизайна представления,
  2. Использование python в основном для части контроллера и модели.

Поэтому QML необходимо взаимодействовать с объектами Python.

Моя проблема: я создал QAbstractListModel в python с помощью следующего (упрощенного) кода:

class MyList(QAbstractListModel):

    _myCol1 = Qt.UserRole + 1
    _myCol2 = Qt.UserRole + 2


    def __init__(self, parent=None):
        super().__init__(parent)
        self.myData= [
            {
                'id': '01',
                'name': 'test1',
            },
            {
                'id': '02',
                'name': 'test2',
            }
        ]

    def data(self, index, role=Qt.DisplayRole):
        row = index.row()
        if role == MyList._myCol1:
            return self.myData[row]['id']
        if role == MyList._myCol2:
            return self.myData[row]['name']

    def rowCount(self, parent=QModelIndex()):
        return len(self.myData)

    def roleNames(self):
        return {
            MyList._myCol1: b'id',
            MyList._myCol2: b'name'
        }

    def get(self, index):
        # How to implement this?

Приведенный выше код работает нормально и предоставляет список из python в QML через QQmlApplicationEngine и rootContext().setContextProperty(...) (я использовал ответ из как автоматически вставлять/редактировать QAbstractListModel в обновлениях python и qml? и документы Qt для Python в качестве ориентира).

При использовании QML ListModel я мог бы использовать функцию object get(index), как описано в документах https://doc.qt.io/qt-5/qml-qtqml-models-listmodel.html. Однако:

  1. Как я могу получить доступ к определенному элементу в экземпляре MyList из QML, поскольку я сделал бы этот элемент с помощью метода get(index), если бы это была собственная модель QML ListModel?
  2. Как реализовать метод get(index)?

Я все еще ищу и ожидаю решения, относящегося к python и QML. Спасибо за вашу помощь!


person BeCurious    schedule 24.11.2019    source источник


Ответы (1)


Только некоторые типы переменных можно экспортировать в QML, среди них str, int, float, list, но в случае со словарем он должен быть экспортирован как QVariant.

С другой стороны, если вы хотите получить доступ к методу из QML, вы должны использовать декоратор @pyqtSlot или @Slot, если вы используете PyQt5 или PySide2, соответственно, указывая тип входных данных, который в данном случае является int, и тип вывод через параметр результата.

main.py

from PySide2 import QtCore, QtGui, QtQml


class MyList(QtCore.QAbstractListModel):
    col1 = QtCore.Qt.UserRole + 1
    col2 = QtCore.Qt.UserRole + 2

    def __init__(self, parent=None):
        super().__init__(parent)
        self.myData = [{"id": "01", "name": "test1",}, {"id": "02", "name": "test2",}]

    def data(self, index, role=QtCore.Qt.DisplayRole):
        row = index.row()
        if index.isValid() and 0 <= row < self.rowCount():
            if role == MyList.col1:
                return self.myData[row]["id"]
            if role == MyList.col2:
                return self.myData[row]["name"]

    def rowCount(self, parent=QtCore.QModelIndex()):
        return len(self.myData)

    def roleNames(self):
        return {MyList.col1: b"id", MyList.col2: b"name"}

    @QtCore.Slot(int, result='QVariant')
    def get(self, row):
        if 0 <= row < self.rowCount():
            return self.myData[row]


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

    app = QtGui.QGuiApplication(sys.argv)

    current_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)))
    qml_file = os.path.join(current_dir, "main.qml")

    model = MyList()

    engine = QtQml.QQmlApplicationEngine()
    engine.rootContext().setContextProperty("listmodel", model)
    engine.load(QtCore.QUrl.fromLocalFile(qml_file))

    sys.exit(app.exec_())

main.qml

import QtQuick 2.13
import QtQuick.Controls 2.13

ApplicationWindow{
    id: root
    visible: true
    width: 640
    height: 480
    ListView{
        id: view
        anchors.fill: parent
        model: listmodel
        delegate: Text{
            text: model.id + " " + model.name
        }
    }
    Component.onCompleted: {
        var obj = listmodel.get(0)
        console.log(obj["id"])
        console.log(obj["name"])
    }
}

Выход:

qml: 01
qml: test1

Плюс:

Только некоторые основные типы принимаются напрямую, и это не относится к dict, в этих случаях вы можете использовать QVariant и QVariantList (для списков или кортежей), но в PySide2 нет QVariant, поэтому вы можете указать типы в C++, передав их как строку: "QVariant". Что указано в документах:

QВариант

As QVariant was removed, any function expecting it can receive any Python object (None is an invalid QVariant). The same rule is valid when returning something: the returned QVariant will be converted to the its original Python object type.

Когда метод ожидает QVariant::Type, программист может использовать строку (имя типа) или сам тип.

(выделено мной)

person eyllanesc    schedule 25.11.2019
comment
Спасибо за Ваш быстрый ответ! Как вы указали, при использовании PySide2 я должен использовать Qt.Core.Slot в качестве декоратора. Но моя проблема в том, что в PySide2 нет типа QVariant. В link под PySide поддерживается только PyQt API 2 (PSEP 101), я обнаружил, что следует использовать собственные типы Python. . Но если я использую result=dict для декоратора функции get, я не могу получить доступ к роли, как вы. С вашим кодом get вернет QVariant(PySide::PyObjectWrapper, ) как obj в qml. - person BeCurious; 25.11.2019
comment
Ok. Я узнал, что мне нужно реализовать ключевое слово result декоратора get следующим образом (по крайней мере, для PySide2): result='QVariant' (нашел здесь: ссылка) Но я не уверен, зачем это делать. Я был бы признателен за некоторые объяснения. И, кстати, задокументировано ли это где-нибудь в официальной документации Qt для Python? До сих пор я находил их не очень полезными. - person BeCurious; 25.11.2019
comment
@BeCurious Как вы понимаете, ответ зависит от привязки: PyQt5 или PySide2, поэтому для следующего вопроса вы должны четко указать, чтобы избежать путаницы. Смотрите мое обновление. - person eyllanesc; 25.11.2019