ItemIsAutoTristate 标志未按预期工作

BPL*_*BPL 3 python checkbox pyqt qtreeview qstandarditemmodel

考虑这个小片段:

import sys

from PyQt5 import QtWidgets
from PyQt5 import QtWidgets
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtGui import QStandardItem
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QGridLayout
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QTreeView
from PyQt5.QtWidgets import QAbstractItemView


packages = {
    'tree': {
        'parent1': ['child1', 'child2', 'child3'],
        'parent2': ['child4', 'child5'],
        'parent3': ['child6']
    },
    'metadata': {
        'child1': {'description': 'child1 description', 'enabled': True},
        'child2': {'description': 'child2 description', 'enabled': False},
        'child3': {'description': 'child3 description', 'enabled': True},
        'child4': {'description': 'child4 description', 'enabled': False},
        'child5': {'description': 'child5 description', 'enabled': True},
        'child6': {'description': 'child6 description', 'enabled': True}
    }
}


class McveDialog(QWidget):

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

        self.treeview = QTreeView()
        # self.treeview.setHeaderHidden(True)
        self.treeview.setUniformRowHeights(True)
        # self.treeview.setEditTriggers(QAbstractItemView.NoEditTriggers)
        # self.treeview.setSelectionMode(QAbstractItemView.ExtendedSelection)

        self.model = QStandardItemModel()
        self.model.setHorizontalHeaderLabels(['Package', 'Description'])

        metadata = packages['metadata']
        tree = packages['tree']
        for parent, childs in tree.items():
            parent_item = QStandardItem(f'{parent}')
            parent_item.setCheckState(True)
            parent_item.setCheckable(True)
            parent_item.setFlags(parent_item.flags() | Qt.ItemIsAutoTristate)
            # parent_item.setFlags(parent_item.flags() | Qt.ItemIsUserTristate)
            self.model.appendRow(parent_item)

            for child in childs:
                description = metadata[child]['description']
                checked = metadata[child]['enabled']
                child_item = QStandardItem(f'{child}')
                check = Qt.Checked if checked else Qt.Unchecked
                child_item.setCheckState(check)
                child_item.setCheckable(True)
                # child_item.setFlags(child_item.flags() |Qt.ItemIsAutoTristate)
                parent_item.appendRow(child_item)

        self.treeview.setModel(self.model)
        self.model.itemChanged.connect(self.on_itemChanged)

        layout = QGridLayout()
        row = 0
        layout.addWidget(self.treeview, row, 0, 1, 3)

        row += 1
        self.but_ok = QPushButton("OK")
        layout.addWidget(self.but_ok, row, 1)
        self.but_ok.clicked.connect(self.on_ok)

        self.but_cancel = QPushButton("Cancel")
        layout.addWidget(self.but_cancel, row, 2)
        self.but_cancel.clicked.connect(self.on_cancel)

        self.setLayout(layout)
        self.setGeometry(300, 200, 460, 350)

    def on_itemChanged(self, item):
        pass

    def on_ok(self):
        pass

    def on_cancel(self):
        self.close()


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    dialog = McveDialog()
    dialog.setWindowTitle('Mcve dialog')
    dialog.show()
    sys.exit(app.exec_())
Run Code Online (Sandbox Code Playgroud)

我在这里想要实现的是,当用户选择所有父级的子级时,父级状态将变为选中状态;如果取消选择所有子项,则取消选择父状态;最后,如果选择了一些孩子,那么父母会被部分选中。(反之亦然,因此如果用户取消选择父项,则其所有子项都将被取消选择,如果用户选择父项,则其所有子项都将被选中)。

理论上,这种行为应该通过使用Qt::ItemIsAutoTristate标志来实现,它说:

项目的状态取决于其子项的状态。这可以自动管理 QTreeWidget 中父项的状态(如果所有子项都被选中,则选中,如果所有子项都未选中,则取消选中,或者如果只选中一些子项,则部分选中)。

但是如果你运行上面的代码,你会发现在阅读文档后你会发现行为不是你所期望的。我已经看到有这个bugreport,虽然我不确定它是否与此有关,或者我的代码段是否只是缺少一些东西。

例如,上面的代码片段允许您这样做:

在此处输入图片说明

无论如何,问题是,您将如何修复此小部件,使其像任何包安装程序一样运行,您可以在其中选择/取消选择/部分选择所有具有共同父级的子包?

ekh*_*oro 5

目前,似乎ItemIsAutoTristate仅针对QTreeWidget该类实施。QStandardItem下面的子类为使用QStandardItemModel. 这是一个或多或少忠实的QTreeWidget实现端口。示例代码似乎可以正常工作,但我还没有对其进行测试:

class StandardItem(QStandardItem):
    def data(self, role = Qt.UserRole + 1):
        if (role == Qt.CheckStateRole and self.hasChildren() and
            self.flags() & Qt.ItemIsAutoTristate):
            return self._childrenCheckState()
        return super().data(role)

    def setData(self, value, role=Qt.UserRole + 1):
        if role == Qt.CheckStateRole:
            if (self.flags() & Qt.ItemIsAutoTristate and
                value != Qt.PartiallyChecked):
                for row in range(self.rowCount()):
                    for column in range(self.columnCount()):
                        child = self.child(row, column)
                        if child.data(role) is not None:
                            flags = self.flags()
                            self.setFlags(flags & ~Qt.ItemIsAutoTristate)
                            child.setData(value, role)
                            self.setFlags(flags)
            model = self.model()
            if model is not None:
                parent = self
                while True:
                    parent = parent.parent()
                    if (parent is not None and
                        parent.flags() & Qt.ItemIsAutoTristate):
                        model.dataChanged.emit(
                            parent.index(), parent.index(),
                            [Qt.CheckStateRole])
                    else:
                        break
        super().setData(value, role)

    def _childrenCheckState(self):
        checked = unchecked = False
        for row in range(self.rowCount()):
            for column in range(self.columnCount()):
                child = self.child(row, column)
                value = child.data(Qt.CheckStateRole)
                if value is None:
                    return
                elif value == Qt.Unchecked:
                    unchecked = True
                elif value == Qt.Checked:
                    checked = True
                else:
                    return Qt.PartiallyChecked
                if unchecked and checked:
                    return Qt.PartiallyChecked
        if unchecked:
            return Qt.Unchecked
        elif checked:
            return Qt.Checked
Run Code Online (Sandbox Code Playgroud)