如何在 Python 和 QML 之间引用自定义角色索引

How to reference custom role indices between Python and QML

我正在 python 中的 class 中实现 QAbstractListModel 以用于 QML。我在模型中定义了两个自定义角色。在模型中,我还实现了一个插槽函数 'get' 到来自指定索引和角色的 return 数据。当我将角色传回 'get' 函数时,我收到一个与我在模型中为角色定义的整数不同的整数。

我尝试将角色和索引从 QML 传回我的模型中定义的 'get' 函数。索引按预期工作,但角色 return 值与我在模型中定义的值不同。

gui.py

main()

def main():
    # create the application instance
    app = QApplication(sys.argv)
    # create a QML engine
    engine = PoaGUI()

    # instantiate Route class
    # route = Route()

    # add example routes for routemodel
    routelist = [
        {'stringinput' : 'Route 1', 'selected' : True},
        {'stringinput' : 'Route 2', 'selected' : False},
        {'stringinput' : 'Route 3', 'selected' : True},
        {'stringinput' : 'Route 4', 'selected' : False}
    ]

    # instantiate ListModel class
    routemodel = ListModel(routelist)

    # register the python type bindings to QML engine
    engine.rootContext().setContextProperty('RouteModel', routemodel)

    # load main QML file and start app engine
    engine.load('view.qml')

    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())

QAbstractListModel

class ListModel(QAbstractListModel):

    # create new user roles as class variables
    StringInput = Qt.UserRole + 0
    Selected = Qt.UserRole + 1
    # ADD USERROLE FOR SELECTED TO COMPLETE

    def __init__(self, datain=[], parent=None):
        super().__init__(parent)

        self._list = datain

    def rowCount(self, parent=QModelIndex()):

        return len(self._list)

    def data(self, index=QModelIndex, role=Qt.DisplayRole):

        #store QModelIndex passed to data
        row = index.row()

        if index.isValid() and role == self.StringInput:
            return self._list[row]['stringinput']
        if index.isValid() and role == self.Selected:
            return self._list[row]['selected']
        else:
            return None

    def roleNames(self):

        return {
            self.StringInput: b'stringinput',
            self.Selected: b'selected'
        }

    @Slot(int, int, result='QVariant')
    def get(self, row, role):
        # show difference between role supplied 
        # from QML vs definition in model
        print(role)
        print('Selected: ' + str(self.Selected))
        print('StringInput: ' + str(self.StringInput))
        if role == self.StringInput:
            print('stringinput')
            return self._list[row]['stringinput']
        elif role == self.Selected:
            print('selected')
            return self._list[row]['selected']
        else:
            return None


    @Slot(int, bool)
    def modSelected(self, row, selval):
        # set index of the row changes
        ix = self.index(row, 0)
        self._list[row]['selected'] = selval
        # communicate that changes were made
        self.dataChanged.emit(ix, ix, self.roleNames())

view.qml

ListView 实现

CustomComp.CustomList {
       id: routelist
       model: RouteModel
       keyNavigationWraps: true

       listwidth: 300
       listheight: 600
       delegate: CustomComp.SingleListDelegate {}

       Layout.alignment: Qt.AlignCenter
    }

ListView 的委托

import QtQuick 2.12
import QtQuick.Controls 2.12

Rectangle {
    id: singleItemList

    property int delegateIndex: index
    property bool altcolor: false
    property string altcolorcode: "#242526"
    property string highlightcolorcode: "#242526"

    width: parent.width; height: 20
    color: (singleItemList.altcolor) ? ((index % 2 == 0) ? altcolorcode:"#000000") : "#000000"
    border.color: "#ffcc00"

    Text {
        id: listText
        anchors.verticalCenter: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter
        font.pixelSize: 14
        color: "#ffcc00"
        // delegate directly uses provided string
        text: stringinput
    }

    MouseArea {
        id: mousearea

        anchors.fill: parent
        onClicked: {
            singleItemList.ListView.view.currentIndex = index
            console.log(singleItemList.ListView.view.currentIndex)
            singleItemList.delegateSelect()
        }
    }

    function delegateSelect() {
        if (singleItemList.ListView.view.model.get(index, selected)) {
            singleItemList.color = "#000000"
            singleItemList.ListView.view.model.modSelected(index, false)
            // singleItemList.ListView.view.model.set(index, {"selected": false})
        }
        else {
            singleItemList.color = highlightcolorcode
            singleItemList.ListView.view.model.modSelected(index, true)
            // singleItemList.ListView.view.model.set(index, {"selected": true})
        }
        // console.log(singleItemList.ListView.view.model.get(index, selected))
    }
}

有两个自定义角色,当我将一个角色从 QML 传递回我的模型时,我收到 0 和 1 顺序的整数。我使用 Qt.UserRole 为我生成的模型定义我的角色编号在 Python。因此,我的模型中定义的那些角色的整数是 256 和 257。QML 似乎可以很好地处理模型,因为当我将模型提供给 ListView 并在委托中引用我的自定义角色之一以显示为文本时,列表会填充正如预期的那样。为什么我的两个自定义角色的整数在 Python 中与在 QML 中不同?既然如此,我如何才能成功 return 那些角色供我的模型中的另一个函数使用?

我的最终目标是在 QML ListView 中创建类似于 'get' 和 'set' 的函数,但这样做是为了我在 Python.[=15 中构建的模型=]

您有一个 XY problem,您的真正目标是从 QML 编辑模型值。因此,解决方案是实施 setData() 方法并使用 selected = foo_value 编辑数据,在您的特定情况下:selected != selected

TL;DR;

你对“selected”的概念有误解,QML中的“selected”是与Selected角色关联的模型的值,就是这个意思:

def roleNames(self):
    return {
        ListModel.StringInput: b"stringinput",
        ListModel.Selected: b"selected",
    }

即QML中选择的不是角色而是角色与索引关联的值,即相当于:

selected = model.data(index, ListModel.Selected)

因此你得到 1 和 0,它们分别是布尔值 true 和 false 的转换。

这样做是为了对角色进行抽象。


所以当你使用下面的时候你就明白了:

QML code               Python Code
foo_value = selected ~ foo_value = model.data(index, ListModel.Selected)

但是当你使用:

QML code               Python Code
selected = foo_value ~ model.setData(index, foo_value, ListModel.Selected)

解决方案

所以在你的情况下,解决方案是:

class ListModel(QAbstractListModel):

    StringInput = Qt.UserRole + 0
    Selected = Qt.UserRole + 1

    def __init__(self, datain=[], parent=None):
        super().__init__(parent)
        self._list = datain

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

    def data(self, index, role=Qt.DisplayRole):
        if not index.isValid():
            return QVariant()
        row = index.row()
        if 0 <= row < self.rowCount():
            if role == ListModel.StringInput:
                return self._list[row]["stringinput"]
            elif role == ListModel.Selected:
                return self._list[row]["selected"]
        return QVariant()

    def setData(self, index, value, role=Qt.EditRole):
        if not index.isValid():
            return False
        row = index.row()
        result = False
        if 0 <= row < self.rowCount():
            if role == ListModel.StringInput:
                self._list[row]["stringinput"] = value
                result = True
            elif role == ListModel.Selected:
                self._list[row]["selected"] = value
                result = True
        if result:
            self.dataChanged.emit(index, index, (role,))
        return result

    def roleNames(self):
        return {
            ListModel.StringInput: b"stringinput",
            ListModel.Selected: b"selected",
        }

SingleListDelegate.qml

import QtQuick 2.12
import QtQuick.Controls 2.12

Rectangle {
    id: singleItemList

    property int delegateIndex: index
    property color highlightcolor: "#242526"
    property color defaultcolor : "#000000"
    width: parent.width; height: 20
    color: selected ? highlightcolor : defaultcolor
    border.color: "#ffcc00"

    Text {
        id: listText
        anchors.verticalCenter: parent.verticalCenter
        anchors.horizontalCenter: parent.horizontalCenter
        font.pixelSize: 14
        color: "#ffcc00"
        text: stringinput
    }

    MouseArea {
        id: mousearea
        anchors.fill: parent
        onClicked: {
            singleItemList.ListView.view.currentIndex = index
            console.log(singleItemList.ListView.view.currentIndex)
            // invert the boolean
            selected = !selected
        }
    }
}