ItemIsAutoTristate标记未按预期工作

时间:2018-03-07 04:18:14

标签: 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_())

我试图在这里实现的是,当用户选择所有父母的孩子时,父状态将被检查;如果取消选择所有子项,则取消选择父状态;最后,如果选择了某些子项,则父项将被部分检查。 (反之亦然,因此如果用户取消选择父项,则将取消选择其所有子项,如果用户选择父项,则其所有子项将被选中)。

理论上,这种行为应该通过使用Qt::ItemIsAutoTristate标志来实现,其中包含:

  

项目的状态取决于其子女的状态。这使得   自动管理QTreeWidget中父项的状态   (如果所有孩子都被检查,则检查,如果所有孩子都未选中,则取消选中   未经检查,或部分检查是否只检查了一些孩子。)

但如果你运行上面的代码,你会发现在阅读文档后,这种行为并不是你所期望的。我已经看到有这个bugreport,虽然我不确定它是否与此相关,或者我的代码片段是否遗漏了一些内容。

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

enter image description here

无论如何,问题是,你将如何修复这个小部件,以便它像任何软件包安装程序一样,你可以选择/取消选择/部分选择所有具有共同父级的子包?

2 个答案:

答案 0 :(得分:1)

目前看来,if parent仅适用于ItemIsAutoTristate类。下面的QTreeWidget子类为使用QStandardItem的项目视图提供相同的功能。这是QStandardItemModel实现的或多或少忠实的端口。它似乎与示例代码一起工作正常,但我还没有测试它死:

QTreeWidget

答案 1 :(得分:1)

正如@ekhumoro的答案和Qt文档中所解释的那样,ItemIsAutoTristate似乎只为QTreeWidget类实现,只是为了完整性,这里有一个小片段,展示如何在QTreeWidget上使用该标志可以开箱即用:

import sys

from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QGridLayout
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtWidgets import QWidget
from PyQt5.QtWidgets import QTreeWidget
from PyQt5.QtWidgets import QTreeWidgetItem

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.treewidget = QTreeWidget()
        self.treewidget.setHeaderLabels(['Package', 'Description'])

        metadata = packages['metadata']
        tree = packages['tree']
        for parent, childs in tree.items():
            parent_item = QTreeWidgetItem(self.treewidget)
            parent_item.setText(0, parent)
            parent_item.setFlags(parent_item.flags() |
                                 Qt.ItemIsAutoTristate | Qt.ItemIsUserCheckable)
            parent_item.setCheckState(0, Qt.Checked)

            for child in childs:
                description = metadata[child]['description']
                checked = metadata[child]['enabled']
                child_item = QTreeWidgetItem(parent_item)
                child_item.setText(0, child)
                child_item.setText(
                    1, packages['metadata'][child]['description'])
                check = Qt.Checked if checked else Qt.Unchecked
                child_item.setFlags(child_item.flags() |
                                    Qt.ItemIsUserCheckable)
                child_item.setCheckState(0, check)

        layout = QGridLayout()
        row = 0
        layout.addWidget(self.treewidget, 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_ok(self):
        self.close()

    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_())