RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1610126
Accepted
Oleg Palamarchuk
Oleg Palamarchuk
Asked:2025-04-09 19:05:00 +0000 UTC2025-04-09 19:05:00 +0000 UTC 2025-04-09 19:05:00 +0000 UTC

带有复选框的 QTreeView 数据透视表

  • 772

PyQt5中有一个代码,它类似于树形表(QTreeView),您可以在其中使用复选框来选择复选框。我需要这个表像这样工作:

  1. 如果所有复选框都被禁用(没有标记复选标记)并且您单击父类,则父类和所有子类都被启用 - 这已经起作用了,完成了;
  2. 如果父复选框的所有子复选框都被禁用,并且您单击一个子复选框,则该子复选框和父复选框都会被启用。其余子复选框不包含在内;
  3. 如果所有复选框都启用(父级和所有子级),并且您启用/禁用一个子级,则只有该子级复选框会发生变化,其余的复选框不会发生变化。

我设法做到了第 1 点和第 3 点,但第 2 点就是行不通。我能做到的唯一方法是,如果我启用一个子复选框(单击它),那么所有子复选框和父复选框都会被启用 - 但它不应该那样工作。
请帮帮我。

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QWidget

import pandas as pd



class Pivot_table(QTreeView):
    def __init__(
            self,
            table_name: str,
            table_width: int,
            columns_width: list,
            data_frame: pd.DataFrame,
            index_cols: list,
            columns_cols: list,
            values_cols: list,
            included_checkBox: bool):
        super().__init__()
        data_frame = data_frame[index_cols]
        pivot_df = data_frame.pivot_table(
            index=index_cols,
            aggfunc='sum',
            fill_value=0)

        header_names = [table_name]
        if columns_cols is not None:
            header_names = header_names + columns_cols
        if values_cols is not None:
            header_names = header_names + values_cols

        self.model_Pivot_table = QStandardItemModel()
        self.model_Pivot_table.setHorizontalHeaderLabels(
            header_names)
        self.model_Pivot_table.itemChanged.connect(
            self.status_checkboxses)

        # Настройка QTreeView
        self.setModel(self.model_Pivot_table)
        self.setFixedWidth(table_width)
        self.setEditTriggers(
            QAbstractItemView.EditTrigger.NoEditTriggers)

        self.populate_model(
            index_cols, pivot_df, included_checkBox)

        if columns_width is not None:  # Установка ширины столбцов
            for column in columns_width:
                self.setColumnWidth(column[0], column[1])

    def populate_model(self, index_cols, pivot_df, included_checkBox):
        parent_items = {}
        for row_index, (index_value, row) in enumerate(pivot_df.iterrows()):
            index_values = index_value \
                if isinstance(index_value, tuple) \
                else (index_value,)
            parent_item = None
            for idx, value in enumerate(index_values):
                if idx == 0:  # Родительский уровень
                    if value not in parent_items:
                        parent_item = QStandardItem(str(value))
                        
                        if included_checkBox:
                            parent_item.setCheckable(True)
                            parent_item.setCheckState(Qt.CheckState.Checked)
                            
                        self.model_Pivot_table.appendRow(parent_item)
                        parent_items[value] = parent_item
                    else:
                        parent_item = parent_items[value]
                
                else:
                    child_found = False
                    for i in range(parent_item.rowCount()):
                        if parent_item.child(i, 0).text() == str(value):
                            child_item = parent_item.child(i, 0)
                            child_found = True
                            break
                    if not child_found:
                        child_item = QStandardItem(str(value))
                        if included_checkBox:
                            child_item.setCheckable(True)
                            child_item.setCheckState(Qt.CheckState.Checked) 
                        parent_item.appendRow(child_item)
                    
                    parent_item = child_item

            # Добавление значения из сводной таблицы
            for col_value in pivot_df.columns:
                value_item = QStandardItem(str(row[col_value]))
                if included_checkBox:
                    value_item.setCheckable(True)
                    value_item.setCheckState(Qt.CheckState.Checked)

                parent_item.appendRow(value_item)

    def status_checkboxses(self, item):
        if not item.parent():
           child_count = item.rowCount()
           for i in range(child_count):
               child_item = item.child(i, 0)
               child_item.setCheckState(item.checkState())


# Основное приложение
if __name__ == '__main__':
    app = QApplication(sys.argv)

    # Данные
    data = {
        0: [1, 2, 3, 3, 4],
        1: [2, 2, 2, 3, 2],
        'ФИО': [
            'Иванов Иван Иванович',
            'Иванов2 Иван2 Иванович2',
            'Иванов3 Иван3 Иванович3',
            'Иванов4 Иван4 Иванович4',
            'Иванов5 Иван5 Иванович5']
    }

    df_residents = pd.DataFrame(data)
    
    widget_apartment = Pivot_table(
        table_name= 'Кв и ФИО',
        table_width= 680,
        columns_width=[[0, 640]],
        data_frame=df_residents,
        index_cols= [1, 'ФИО'],
        columns_cols= None,
        values_cols= None,
        included_checkBox= True)

    # Устанавливаем виджет в главное окно
    central_widget = QWidget()
    layout = QVBoxLayout()
    layout.addWidget(widget_apartment)
    central_widget.setLayout(layout)

    main_window = QMainWindow()
    main_window.setGeometry(100, 100, 800, 600)
    main_window.setCentralWidget(central_widget)
    main_window.show()
    
    sys.exit(app.exec_())
python
  • 1 1 个回答
  • 29 Views

1 个回答

  • Voted
  1. Best Answer
    S. Nick
    2025-04-10T05:46:31Z2025-04-10T05:46:31Z

    如果我理解正确的话,您需要实现三态机制。

    这可以通过重写两种方法来实现QStandardItemModel:

    • setData(index, value, role)- 覆盖以应用检查状态;
    • data(index, role)- 需要显示父母的控制状态。

    主程序

    import sys
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class TreeModel(QtGui.QStandardItemModel):
        checkStateChange = QtCore.pyqtSignal(QtCore.QModelIndex, bool)
        
        def __init__(self, dataSet):
            super(TreeModel, self).__init__()
    
            for page_name, page_contents in dataSet.items():
                for pk, pv in page_contents.items():
                    parent = QtGui.QStandardItem(pk)
                    parent.setCheckable(True)
                    self.appendRow(parent)
                    if pv:
                        parent.setTristate(True)
                        for c in pv:
                            child = QtGui.QStandardItem(c)
                            child.setCheckable(True)
                            parent.appendRow(child)
    
            self.dataChanged.connect(self.checkStateChange)
            self.setHorizontalHeaderLabels(['Кв и ФИО'])
    
        def setData(self, index, value, role=QtCore.Qt.EditRole):
            if role == QtCore.Qt.CheckStateRole:
                childState = QtCore.Qt.Checked if value \
                                               else QtCore.Qt.Unchecked
                # установить все children состояния в соответствии 
                # с parent item
                for row in range(self.rowCount(index)):
                    for col in range(self.columnCount(index)):
                        childIndex = self.index(row, col, index)
                        self.setData(
                            childIndex, childState, 
                            QtCore.Qt.CheckStateRole)
                # если у элемента есть родительский элемент, 
                # подайте сигнал dataChanged, чтобы убедиться, 
                # что родительское состояние отображается правильно 
                # в соответствии с тем, что data() вернет; 
                parent = self.parent(index)
                if parent.isValid():
                    self.dataChanged.emit(parent, parent)
            return super(TreeModel, self).setData(index, value, role)
    
        def data(self, index, role=QtCore.Qt.DisplayRole):
            # QStandardItemModel не поддерживает автоматическое Tristate, 
            # основанное на его дочерних элементах, как это происходит 
            # для внутренней модели QTreeWidget. 
            # Надо реализовать это:
            if role == QtCore.Qt.CheckStateRole and \
                self.flags(index) & QtCore.Qt.ItemIsTristate:
                childStates = []
                # собрать все дочерние состояния проверки
                for row in range(self.rowCount(index)):
                    for col in range(self.columnCount(index)):
                        childIndex = self.index(row, col, index)
                        childState = self.data(childIndex, 
                            QtCore.Qt.CheckStateRole)
                        # если состояние child частично проверено, 
                        # мы можем остановиться и 
                        # вернуть частично проверенное состояние
                        if childState == QtCore.Qt.PartiallyChecked:
                            return QtCore.Qt.PartiallyChecked
                        childStates.append(childState)
                if all(childStates):
                    # все children проверены
                    return QtCore.Qt.Checked
                elif any(childStates):
                    # только некоторые children проверены ...
                    return QtCore.Qt.PartiallyChecked
                return QtCore.Qt.Unchecked
            return super(TreeModel, self).data(index, role)
    
        def checkStateChange(self, topLeft, bottomRight):
            pass
    
    
    # Основное приложение
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__()
            self.central_widget = QtWidgets.QWidget()
            self.setCentralWidget(self.central_widget)
            
            self.treeView = QtWidgets.QTreeView()
            
            layout = QtWidgets.QGridLayout(self.central_widget)
            layout.addWidget(self.treeView)
    
            model = TreeModel(dataSet)
            self.setModel(model)
    
        def getExpandState(self, expDict, model, index=QtCore.QModelIndex()):
            # установите expanded state индекса, 
            # если это не корневой индекс: 
            # корневой индекс не является допустимым индексом!
            if index.isValid():
                expDict[index] = self.treeView.isExpanded(index)
            # если у индекса (или корневого индекса) есть дочерние элементы, 
            # установите их состояния
            for row in range(model.rowCount(index)):
                for col in range(model.columnCount(index)):
                    childIndex = model.index(row, col, index)
                    # если у текущего индекса есть дочерние элементы, 
                    # установите их expand state, 
                    # используя эту функцию, которая является рекурсивной
                    for childRow in range(model.rowCount(childIndex)):
                        self.getExpandState(expDict, model, childIndex)
    
        def setModel(self, model):
            self.treeView.setModel(model)
            self.treeView.expandAll() 
    
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        app.setFont(QtGui.QFont("Times", 12, QtGui.QFont.Bold))
    
        dataSet = {
            "itemA" :{
                "группа A": ["a101", "a102"],
            },
            "itemBC": {
                "группа B": ["b101"],
                "группа C": ["c101", "c102", "c103"],
            },
            "itemD" :{
                "группа D": ["d100"],
            },
        }    
        
        window = MainWindow()
        window.setWindowTitle("QTreeView Example")
        window.resize(300, 400)    
        window.show()
        sys.exit(app.exec())
    

    在此处输入图片描述

    • 0

相关问题

  • 是否可以以某种方式自定义 QTabWidget?

  • telebot.anihelper.ApiException 错误

  • Python。检查一个数字是否是 3 的幂。输出 无

  • 解析多个响应

  • 交换两个数组的元素,以便它们的新内容也反转

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5