qtquick/qml从pyqt5创建可扩展的子菜单

发布于 2025-02-13 11:17:19 字数 11905 浏览 0 评论 0 原文

我想创建ListView,其中包含用于扩展和折叠子菜单的嵌套ListModel。 (在此处,我在。)。

我的问题是可扩展的菜单是在我创建Python/pyqt5的ListModel时没有响应的。但是,如果我在QML侧创建它,则扩展操作正常运行。 (但是我不想在qml侧创建它,因为我必须在后端进行操作。)

这是main.py

import sys
from PyQt5.QtQml    import QQmlApplicationEngine, qmlRegisterType
from PyQt5.QtGui    import QGuiApplication
from PyQt5.QtCore   import QTimer, QObject, pyqtSignal, pyqtSlot, QAbstractListModel, QModelIndex, Qt, pyqtProperty

class Backend(QObject):
    modelChanged = pyqtSignal()

    def __init__(self):
        super().__init__()
        self._model = MyListModel()                                   

    ##~~Expose model as a property of our backend~~##
    @pyqtProperty(QObject, constant=False, notify=modelChanged)         
    def model(self):                                                   
        return self._model                                              

class MyListModel(QAbstractListModel):
    ##~~My Custom UserRoles~~##
    NameRole = Qt.UserRole + 1000
    CollapsedRole = Qt.UserRole + 1001
    SubItemsRole = Qt.UserRole + 1002

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

    def rowCount(self, parent=None, *args, **kwargs):
        return len(self.itemNames)

    def data(self, index, role=Qt.DisplayRole):
        if 0 <= index.row() < self.rowCount() and index.isValid():
            item = self.itemNames[index.row()]

            if role == MyListModel.NameRole:
                return item['assetName']
            elif role == MyListModel.SubItemsRole:
                return item['subItems']
            elif role == MyListModel.CollapsedRole:
                return item['isCollapsed']

    def roleNames(self):
        roles = dict()
        roles[MyListModel.NameRole] = b'assetName'
        roles[MyListModel.SubItemsRole] = b'subItems'
        roles[MyListModel.CollapsedRole] = b'isCollapsed'
        return roles

    @pyqtSlot(str, bool)
    def appendRow(self, name, isCollapsed):
        self.subItem = MySubListModel()
        self.subItem.addRow()
        self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
        self.itemNames.append({'assetName': name, 'subItems': self.subItem, 'isCollapsed': isCollapsed})
        self.endInsertRows()
        print(self.itemNames)
        print(self.subItem.subItemParams)

    @pyqtSlot(int, str)
    def collapseEditInputsMenu(self, index, modelIndexName):
        self.itemNames[index][modelIndexName] = not self.itemNames[index][modelIndexName]
        print(f"From Backend: {self.itemNames}")

class MySubListModel(QAbstractListModel):
    ##~~My Custom UserRole For SubItem ListModel~~##
    CellSizeRole = Qt.UserRole + 1004

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

    def rowCount(self, parent=None, *args, **kwargs):
        return len(self.subItemParams)

    def data(self, index, role=Qt.DisplayRole):
        if 0 <= index.row() < self.rowCount() and index.isValid():
            item = self.subItemParams[index.row()]

            if role == MySubListModel.CellSizeRole:
                return item['cellSize']

    def roleNames(self):
        roles = dict()
        roles[MySubListModel.CellSizeRole] = b'cellSize'
        return roles

    def addRow(self):
        self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
        self.subItemParams.append({'cellSize': "888"})
        self.endInsertRows() 



class MainWindow():
    def __init__(self):
        app = QGuiApplication(sys.argv)
        self.engine = QQmlApplicationEngine()
        self.engine.quit.connect(app.quit)
        self.engine.load("main.qml")

        app_backend = Backend()
        self.engine.rootObjects()[0].setProperty("backendObjectInQML", app_backend)

        sys.exit(app.exec())




def main():
    window = MainWindow()

if __name__ == '__main__':
    main()

...和main.qml

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.15

ApplicationWindow {
    id: myApplicationWindow
    title: "Expandable ListView App"
    visible: true
    height: 400
    width: 400

    property QtObject backendObjectInQML

    Rectangle {
        id: rootRectangle
        color: "grey"
        anchors.fill: parent

        Item {
            id: solutionFileListViewRoot
            anchors.fill: parent
            // ListModel {
            //     id: myNestedListModel
            //     ListElement {
            //         assetName: "Dummy Item"
            //         isCollapsed: true
            //         subItems: [ListElement {cellSize: "888"}]
            //     }
            // }
            ListView {
                id: myNestedListView
                anchors {
                    top: parent.top
                    left: parent.left
                    right: parent.right
                    bottom: parent.bottom
                    bottomMargin: 50
                }
                model: myApplicationWindow.backendObjectInQML.model
                // model: myNestedListModel
                delegate: myAppListElementDelegate
                spacing: 6
                clip: true
                ScrollBar.vertical: ScrollBar {
                    active: true
                }
            }
        }

        Component {
            id: myAppListElementDelegate
            Column {
                id: listElementColumn
                width: myNestedListView.width
                Rectangle {
                    id: listElementRectangle
                    height: 30
                    anchors {
                        left: parent.left
                        right: parent.right
                        rightMargin: 15
                        leftMargin: 15
                    }
                    color: "yellow"
                    radius: 3
                    Text {
                        height: 24
                        width: 100
                        text: assetName
                        anchors {
                            verticalCenter: parent.verticalCenter
                            left: parent.left
                        }
                        horizontalAlignment: Text.AlignLeft
                        verticalAlignment: Text.AlignVCenter
                        color: "black"
                    }
                    Button {
                        id: expandButton
                        width: 70
                        height: 24
                        text: "Expand"
                        anchors {
                            right: parent.right
                            rightMargin: 20
                            verticalCenter: parent.verticalCenter
                        }
                        onClicked: {
                            myNestedListView.currentIndex = index
                            myApplicationWindow.backendObjectInQML.model.collapseEditInputsMenu(index, "isCollapsed")
                            // myNestedListModel.setProperty(index, "isCollapsed", !isCollapsed)
                            console.log("From QML isCollapsed:")
                            console.log(isCollapsed)
                        }
                    }
                }
                Loader {
                    id: subSolutionEditItemLoader
                    visible: !isCollapsed
                    property variant subEditItemModel: subItems
                    sourceComponent: isCollapsed ? null : subItemEditInputsDelegate
                    onStatusChanged: {
                        // console.log(subItems)
                        if(status == Loader.Ready) item.model = subEditItemModel
                    }
                }
            }
        }

        Component {
            id: subItemEditInputsDelegate

            Column {
                property alias model: subItemRepeater.model
                id: nestedListElementColumn
                width: myNestedListView.width
                anchors {
                    top: parent.top
                    topMargin: 3
                }
                spacing: 3

                Repeater {
                    id: subItemRepeater
                    width: parent.width
                    delegate: Rectangle {
                        id: nestedListElementRectangle
                        color: "blue"
                        height: 40
                        anchors {
                            left: parent.left
                            leftMargin: 30
                            right: parent.right
                            rightMargin: 30
                        }
                        radius: 5

                        Rectangle {
                            id: cellSizeBackground
                            height: 20
                            width: cellSizeLabel.implicitWidth
                            color: "#00000000"
                            anchors {
                                left: parent.left
                                leftMargin: 25
                                top: parent.top
                                topMargin: 10
                            }
                            Label {
                                id: cellSizeLabel
                                text: "Cell Size: "
                                anchors.fill: parent
                                verticalAlignment: Text.AlignVCenter
                                color: "#6e95bc"
                            }
                        }

                        Rectangle {
                            id: cellSizeTextInputBorder
                            height: 24
                            width: 120
                            color: "#00000000"
                            radius: 5
                            anchors {
                                left: cellSizeBackground.right
                                leftMargin: 10
                                verticalCenter: cellSizeBackground.verticalCenter
                            }
                            border.width: 1
                            border.color: "#12C56A"

                            TextInput {
                                id: cellSizeTextInput
                                text: cellSize
                                verticalAlignment: Text.AlignVCenter
                                anchors.fill: parent
                                color: "#6e95bc"
                                selectByMouse: true
                                leftPadding: 5
                                rightPadding: 5
                                clip: true

                                onEditingFinished: {
                                    console.log("cellSizeTextInput edited...")
                                }
                            }
                        }
                    }
                }
            }
        }

        Button {
            id: addListElementButton
            height: 24
            width: 70
            text: "Add"
            anchors {
                bottom: parent.bottom
                right: parent.right
            }
            onClicked: {
                myApplicationWindow.backendObjectInQML.model.appendRow("Dummy Item", false)
            }
        }
    }
}

。没错,它已在listView上进行更新,但以某种方式没有触发GUI。但是我不知道为什么。

我还为QML添加了嵌套的ListModel和相关行,如想要尝试使用QML端创建的ListModel的代码的人所注释的。

您可以在下面看到应用程序屏幕截图:

”

I want to create ListView which includes nested ListModel for expanding and collapsing submenu. (The topic that I use while I creating nested expandable listview in here.)

My problem is expandable menu is not responding when I create it's ListModel from Python/PyQt5. But if I create it in QML side, expanding operation is working smoothly. (But I don't want create it on QML side cause I have to manipulate it on backend.)

Here is the main.py

import sys
from PyQt5.QtQml    import QQmlApplicationEngine, qmlRegisterType
from PyQt5.QtGui    import QGuiApplication
from PyQt5.QtCore   import QTimer, QObject, pyqtSignal, pyqtSlot, QAbstractListModel, QModelIndex, Qt, pyqtProperty

class Backend(QObject):
    modelChanged = pyqtSignal()

    def __init__(self):
        super().__init__()
        self._model = MyListModel()                                   

    ##~~Expose model as a property of our backend~~##
    @pyqtProperty(QObject, constant=False, notify=modelChanged)         
    def model(self):                                                   
        return self._model                                              

class MyListModel(QAbstractListModel):
    ##~~My Custom UserRoles~~##
    NameRole = Qt.UserRole + 1000
    CollapsedRole = Qt.UserRole + 1001
    SubItemsRole = Qt.UserRole + 1002

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

    def rowCount(self, parent=None, *args, **kwargs):
        return len(self.itemNames)

    def data(self, index, role=Qt.DisplayRole):
        if 0 <= index.row() < self.rowCount() and index.isValid():
            item = self.itemNames[index.row()]

            if role == MyListModel.NameRole:
                return item['assetName']
            elif role == MyListModel.SubItemsRole:
                return item['subItems']
            elif role == MyListModel.CollapsedRole:
                return item['isCollapsed']

    def roleNames(self):
        roles = dict()
        roles[MyListModel.NameRole] = b'assetName'
        roles[MyListModel.SubItemsRole] = b'subItems'
        roles[MyListModel.CollapsedRole] = b'isCollapsed'
        return roles

    @pyqtSlot(str, bool)
    def appendRow(self, name, isCollapsed):
        self.subItem = MySubListModel()
        self.subItem.addRow()
        self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
        self.itemNames.append({'assetName': name, 'subItems': self.subItem, 'isCollapsed': isCollapsed})
        self.endInsertRows()
        print(self.itemNames)
        print(self.subItem.subItemParams)

    @pyqtSlot(int, str)
    def collapseEditInputsMenu(self, index, modelIndexName):
        self.itemNames[index][modelIndexName] = not self.itemNames[index][modelIndexName]
        print(f"From Backend: {self.itemNames}")

class MySubListModel(QAbstractListModel):
    ##~~My Custom UserRole For SubItem ListModel~~##
    CellSizeRole = Qt.UserRole + 1004

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

    def rowCount(self, parent=None, *args, **kwargs):
        return len(self.subItemParams)

    def data(self, index, role=Qt.DisplayRole):
        if 0 <= index.row() < self.rowCount() and index.isValid():
            item = self.subItemParams[index.row()]

            if role == MySubListModel.CellSizeRole:
                return item['cellSize']

    def roleNames(self):
        roles = dict()
        roles[MySubListModel.CellSizeRole] = b'cellSize'
        return roles

    def addRow(self):
        self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
        self.subItemParams.append({'cellSize': "888"})
        self.endInsertRows() 



class MainWindow():
    def __init__(self):
        app = QGuiApplication(sys.argv)
        self.engine = QQmlApplicationEngine()
        self.engine.quit.connect(app.quit)
        self.engine.load("main.qml")

        app_backend = Backend()
        self.engine.rootObjects()[0].setProperty("backendObjectInQML", app_backend)

        sys.exit(app.exec())




def main():
    window = MainWindow()

if __name__ == '__main__':
    main()

... and main.qml

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtGraphicalEffects 1.15

ApplicationWindow {
    id: myApplicationWindow
    title: "Expandable ListView App"
    visible: true
    height: 400
    width: 400

    property QtObject backendObjectInQML

    Rectangle {
        id: rootRectangle
        color: "grey"
        anchors.fill: parent

        Item {
            id: solutionFileListViewRoot
            anchors.fill: parent
            // ListModel {
            //     id: myNestedListModel
            //     ListElement {
            //         assetName: "Dummy Item"
            //         isCollapsed: true
            //         subItems: [ListElement {cellSize: "888"}]
            //     }
            // }
            ListView {
                id: myNestedListView
                anchors {
                    top: parent.top
                    left: parent.left
                    right: parent.right
                    bottom: parent.bottom
                    bottomMargin: 50
                }
                model: myApplicationWindow.backendObjectInQML.model
                // model: myNestedListModel
                delegate: myAppListElementDelegate
                spacing: 6
                clip: true
                ScrollBar.vertical: ScrollBar {
                    active: true
                }
            }
        }

        Component {
            id: myAppListElementDelegate
            Column {
                id: listElementColumn
                width: myNestedListView.width
                Rectangle {
                    id: listElementRectangle
                    height: 30
                    anchors {
                        left: parent.left
                        right: parent.right
                        rightMargin: 15
                        leftMargin: 15
                    }
                    color: "yellow"
                    radius: 3
                    Text {
                        height: 24
                        width: 100
                        text: assetName
                        anchors {
                            verticalCenter: parent.verticalCenter
                            left: parent.left
                        }
                        horizontalAlignment: Text.AlignLeft
                        verticalAlignment: Text.AlignVCenter
                        color: "black"
                    }
                    Button {
                        id: expandButton
                        width: 70
                        height: 24
                        text: "Expand"
                        anchors {
                            right: parent.right
                            rightMargin: 20
                            verticalCenter: parent.verticalCenter
                        }
                        onClicked: {
                            myNestedListView.currentIndex = index
                            myApplicationWindow.backendObjectInQML.model.collapseEditInputsMenu(index, "isCollapsed")
                            // myNestedListModel.setProperty(index, "isCollapsed", !isCollapsed)
                            console.log("From QML isCollapsed:")
                            console.log(isCollapsed)
                        }
                    }
                }
                Loader {
                    id: subSolutionEditItemLoader
                    visible: !isCollapsed
                    property variant subEditItemModel: subItems
                    sourceComponent: isCollapsed ? null : subItemEditInputsDelegate
                    onStatusChanged: {
                        // console.log(subItems)
                        if(status == Loader.Ready) item.model = subEditItemModel
                    }
                }
            }
        }

        Component {
            id: subItemEditInputsDelegate

            Column {
                property alias model: subItemRepeater.model
                id: nestedListElementColumn
                width: myNestedListView.width
                anchors {
                    top: parent.top
                    topMargin: 3
                }
                spacing: 3

                Repeater {
                    id: subItemRepeater
                    width: parent.width
                    delegate: Rectangle {
                        id: nestedListElementRectangle
                        color: "blue"
                        height: 40
                        anchors {
                            left: parent.left
                            leftMargin: 30
                            right: parent.right
                            rightMargin: 30
                        }
                        radius: 5

                        Rectangle {
                            id: cellSizeBackground
                            height: 20
                            width: cellSizeLabel.implicitWidth
                            color: "#00000000"
                            anchors {
                                left: parent.left
                                leftMargin: 25
                                top: parent.top
                                topMargin: 10
                            }
                            Label {
                                id: cellSizeLabel
                                text: "Cell Size: "
                                anchors.fill: parent
                                verticalAlignment: Text.AlignVCenter
                                color: "#6e95bc"
                            }
                        }

                        Rectangle {
                            id: cellSizeTextInputBorder
                            height: 24
                            width: 120
                            color: "#00000000"
                            radius: 5
                            anchors {
                                left: cellSizeBackground.right
                                leftMargin: 10
                                verticalCenter: cellSizeBackground.verticalCenter
                            }
                            border.width: 1
                            border.color: "#12C56A"

                            TextInput {
                                id: cellSizeTextInput
                                text: cellSize
                                verticalAlignment: Text.AlignVCenter
                                anchors.fill: parent
                                color: "#6e95bc"
                                selectByMouse: true
                                leftPadding: 5
                                rightPadding: 5
                                clip: true

                                onEditingFinished: {
                                    console.log("cellSizeTextInput edited...")
                                }
                            }
                        }
                    }
                }
            }
        }

        Button {
            id: addListElementButton
            height: 24
            width: 70
            text: "Add"
            anchors {
                bottom: parent.bottom
                right: parent.right
            }
            onClicked: {
                myApplicationWindow.backendObjectInQML.model.appendRow("Dummy Item", false)
            }
        }
    }
}

I suspect that when I update "isCollapsed" item of listModel from false to true, it is updated on listView but somehow it is not triggering the GUI. But I don't know why.

I also added Nested ListModel and relevant lines to QML as commented out for ones who want to try the code with ListModel created on QML side.

You can see application ScreenShot below:

App ScreenShot

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

梦罢 2025-02-20 11:17:19

您在更改倒塌状态时没有发出任何信号
因此,无法理解该角色的财产可以知道它们需要与之同步。

更改:

@pyqtSlot(int, str)
def collapseEditInputsMenu(self, index, modelIndexName):
    self.itemNames[index][modelIndexName] = not self.itemNames[index][modelIndexName]
    print(f"From Backend: {self.itemNames}")

到:


@pyqtSlot(int, str)
def collapseEditInputsMenu(self, index, modelIndexName):
    self.layoutAboutToBeChanged.emit()
    self.itemNames[index][modelIndexName] = not self.itemNames[index][modelIndexName]
    print(f"From Backend: {self.itemNames}")
    self.layoutChanged.emit()


ps:这是非常混乱的代码,我在这里列出一些严重的错误:

第一:

self.Engine.rootobjects()[0] .setProperty(“ BackendObjectInqml”,app_backend)

属性qtObject backendobjectInqml

这是多余的,您应该使用:

self.engine.rootContext().setContextProperty("backendObjectInQML", app_backend)

第二:

myApplicationWindow.backendObjectInqml.model

如果您将使用 context> context property ,如我所说的,您无需访问它, myApplicationWindow

third third:
iScollapsed 来自模型的角色可能是冗余的,您应该更喜欢为 myAppListelementDelegate component创建属性。
这样的事情:

Component {
    id: myAppListElementDelegate
    Column {
        id: listElementColumn
        property bool isCollapsed: false  // <<<---
        width: myNestedListView.width
        Rectangle {
            id: listElementRectangle
            height: 30
            anchors {
                left: parent.left
                right: parent.right
                rightMargin: 15
                leftMargin: 15
            }
            color: "yellow"
            radius: 3
            Text {
                height: 24
                width: 100
                text: assetName
                anchors {
                    verticalCenter: parent.verticalCenter
                    left: parent.left
                }
                horizontalAlignment: Text.AlignLeft
                verticalAlignment: Text.AlignVCenter
                color: "black"
            }
            Button {
                id: expandButton
                width: 70
                height: 24
                text: "Expand"
                anchors {
                    right: parent.right
                    rightMargin: 20
                    verticalCenter: parent.verticalCenter
                }
                onClicked: isCollapsed = !isCollapsed
            }
        }
        Loader {
            id: subSolutionEditItemLoader
            visible: !isCollapsed
            property variant subEditItemModel: subItems
            sourceComponent: isCollapsed ? null: subItemEditInputsDelegate
            onStatusChanged: {
                // console.log(subItems)
                if(status == Loader.Ready) item.model = subEditItemModel
            }
        }
    }
}

You didn't emitted any signals while changing the collapsed state.
Therefore there was no way for properties that relay on that role to know that they need to synchronize with it.

Change:

@pyqtSlot(int, str)
def collapseEditInputsMenu(self, index, modelIndexName):
    self.itemNames[index][modelIndexName] = not self.itemNames[index][modelIndexName]
    print(f"From Backend: {self.itemNames}")

To:


@pyqtSlot(int, str)
def collapseEditInputsMenu(self, index, modelIndexName):
    self.layoutAboutToBeChanged.emit()
    self.itemNames[index][modelIndexName] = not self.itemNames[index][modelIndexName]
    print(f"From Backend: {self.itemNames}")
    self.layoutChanged.emit()

Fully working example


P.S: This is very messy code, I'll list you here a few severe mistakes:

First:

self.engine.rootObjects()[0].setProperty("backendObjectInQML", app_backend)

property QtObject backendObjectInQML

This is redundant you should use instead:

self.engine.rootContext().setContextProperty("backendObjectInQML", app_backend)

Second:

myApplicationWindow.backendObjectInQML.model

If you will use contextProperty as I said above you don't need to access it threw myApplicationWindow

Third:
The isCollapsed role from your model is probably redundant and you should have preferred creating a property for your myAppListElementDelegate component.
Something like this:

Component {
    id: myAppListElementDelegate
    Column {
        id: listElementColumn
        property bool isCollapsed: false  // <<<---
        width: myNestedListView.width
        Rectangle {
            id: listElementRectangle
            height: 30
            anchors {
                left: parent.left
                right: parent.right
                rightMargin: 15
                leftMargin: 15
            }
            color: "yellow"
            radius: 3
            Text {
                height: 24
                width: 100
                text: assetName
                anchors {
                    verticalCenter: parent.verticalCenter
                    left: parent.left
                }
                horizontalAlignment: Text.AlignLeft
                verticalAlignment: Text.AlignVCenter
                color: "black"
            }
            Button {
                id: expandButton
                width: 70
                height: 24
                text: "Expand"
                anchors {
                    right: parent.right
                    rightMargin: 20
                    verticalCenter: parent.verticalCenter
                }
                onClicked: isCollapsed = !isCollapsed
            }
        }
        Loader {
            id: subSolutionEditItemLoader
            visible: !isCollapsed
            property variant subEditItemModel: subItems
            sourceComponent: isCollapsed ? null: subItemEditInputsDelegate
            onStatusChanged: {
                // console.log(subItems)
                if(status == Loader.Ready) item.model = subEditItemModel
            }
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文