You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5中QTreeView按原始顺序及逆序排序失败问题求助

解决QTreeView按原始顺序及逆序排序的问题

你的问题核心是没有为模型中的每个项正确设置Qt.UserRole对应的原始顺序索引,导致切换到该角色排序时没有有效数据可用。以下是具体的修复方案:

1. 为所有项添加原始顺序索引(Qt.UserRole)

在模型初始化创建每个QStandardItem时,为其设置Qt.UserRole数据,记录该项的原始创建顺序。这样排序时就能依据这个值恢复原始顺序。

修改MyModel的__init__方法,添加顺序计数逻辑:

class MyModel(QStandardItemModel):
    def __init__(self):
        super().__init__()

        self.root_text, self.parent_text, self.child_text = [], [], []

        # 为每个层级的项记录原始顺序索引
        root_order = 0
        for root_key, root_value in data.items():
            if root_key not in self.root_text:
                self.root_text.append(root_key)
            root_item = QStandardItem()
            root_item.setData(root_key, role=Qt.DisplayRole)
            # 设置根项的原始顺序索引
            root_item.setData(root_order, role=Qt.UserRole)
            root_item.setCheckable(True)
            self.appendRow(root_item)
            root_order += 1

            parent_order = 0
            for parent_key, parent_value in root_value.items():
                if parent_key not in self.parent_text:
                    self.parent_text.append(parent_key)
                parent_item = QStandardItem()
                parent_item.setData(parent_key, role=Qt.DisplayRole)
                # 设置组项的原始顺序索引
                parent_item.setData(parent_order, role=Qt.UserRole)
                parent_item.setCheckable(True)
                root_item.appendRow(parent_item)
                parent_order += 1

                child_order = 0
                for child_value in parent_value:
                    if child_value not in self.child_text:
                        self.child_text.append(child_value)
                    child_item = QStandardItem()
                    child_item.setData(child_value, role=Qt.DisplayRole)
                    # 设置子项的原始顺序索引
                    child_item.setData(child_order, role=Qt.UserRole)
                    child_item.setCheckable(True)
                    parent_item.appendRow(child_item)
                    child_order += 1

        self.itemChanged.connect(self.update_children)

    # 保留原有的update_children方法...

2. 修正排序函数逻辑

确保排序时正确切换到Qt.UserRole,并且每个层级的排序都能生效。你的original_order和reverse_order方法无需大改,但可以明确指定排序的列(这里是0列):

def original_order(self):
    self.model_01.setSortRole(Qt.UserRole)
    self.model_01.sort(0, Qt.AscendingOrder)

def reverse_order(self):
    self.model_01.setSortRole(Qt.UserRole)
    self.model_01.sort(0, Qt.DescendingOrder)

3. 关键说明

  • 每个层级的项(根、组、子项)都需要独立的顺序索引,因为QTreeView的排序是按父项的子项层级分别进行的。
  • Qt.UserRole是自定义数据的默认起始角色,用来存储原始顺序这种业务数据非常合适。
  • 设置SortRole后调用sort(),模型会自动根据指定角色的数据排序对应层级的子项。

完整修改后的代码

将上述修改整合到你的代码中,完整代码如下:

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

data = {
    "select all": {
        'Group 1': ['item11', 'item12'],
        'Group 3': ['item32', 'item31'],
        'Group 2': ['item21', 'item22'],
        'Group 4': ['item41', 'item42'],
    }
}
class MyModel(QStandardItemModel):
    def __init__(self):
        super().__init__()

        self.root_text, self.parent_text, self.child_text = [], [], []

        root_order = 0
        for root_key, root_value in data.items():
            if root_key not in self.root_text:
                self.root_text.append(root_key)
            root_item = QStandardItem()
            root_item.setData(root_key, role=Qt.DisplayRole)
            root_item.setData(root_order, role=Qt.UserRole)
            root_item.setCheckable(True)
            self.appendRow(root_item)
            root_order += 1

            parent_order = 0
            for parent_key, parent_value in root_value.items():
                if parent_key not in self.parent_text:
                    self.parent_text.append(parent_key)
                parent_item = QStandardItem()
                parent_item.setData(parent_key, role=Qt.DisplayRole)
                parent_item.setData(parent_order, role=Qt.UserRole)
                parent_item.setCheckable(True)
                root_item.appendRow(parent_item)
                parent_order += 1

                child_order = 0
                for child_value in parent_value:
                    if child_value not in self.child_text:
                        self.child_text.append(child_value)
                    child_item = QStandardItem()
                    child_item.setData(child_value, role=Qt.DisplayRole)
                    child_item.setData(child_order, role=Qt.UserRole)
                    child_item.setCheckable(True)
                    parent_item.appendRow(child_item)
                    child_order += 1

        self.itemChanged.connect(self.update_children)

    def update_children(self, item, fromUser=True):
        if fromUser:
            self.itemChanged.disconnect(self.update_children)
        for i in range(item.rowCount()):
            child = item.child(i)
            child.setCheckState(item.checkState())
            self.update_children(child, False)

        if fromUser:
            root = self.invisibleRootItem()
            parent = item.parent() or root
            while True:
                count = parent.rowCount()
                checked = 0
                partial = False
                for i in range(count):
                    state = parent.child(i).checkState()
                    if state == Qt.Checked:
                        checked += 1
                    elif state == Qt.PartiallyChecked:
                        partial = True
                        break
                if partial:
                    parent.setCheckState(Qt.PartiallyChecked)
                else:
                    if not checked:
                        parent.setCheckState(Qt.Unchecked)
                    elif checked == count:
                        parent.setCheckState(Qt.Checked)
                    else:
                        parent.setCheckState(Qt.PartiallyChecked)

                if parent == root:
                    break
                parent = parent.parent() or root

            self.itemChanged.connect(self.update_children)

class MyCombo(QComboBox):
    clickedData = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.treeView = QTreeView()
        self.treeView.setHeaderHidden(True)
        self.setView(self.treeView)
        self.treeView.viewport().installEventFilter(self)

        self.menu = QMenu()
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.cntxt_menu)
        self.RightClickMenu()

        self.delegate = QStyledItemDelegate(self.treeView)

    def eventFilter(self, obj, event):
        if (
                event.type() == event.MouseButtonPress
                and event.button() == Qt.LeftButton
        ):
            index = self.treeView.indexAt(event.pos())

            if index.isValid():
                opt = self.treeView.viewOptions()
                opt.rect = self.treeView.visualRect(index)
                self.delegate.initStyleOption(opt, index)
                checkRect = self.style().subElementRect(
                    QStyle.SE_ItemViewItemCheckIndicator, opt, self.treeView)
                if checkRect.contains(event.pos()):
                    self.clickedData = index, checkRect
        elif event.type() == event.MouseButtonRelease:
            if event.button() == Qt.LeftButton and self.clickedData:
                index = self.treeView.indexAt(event.pos())
                pressIndex, checkRect = self.clickedData
                if index == pressIndex and event.pos() in checkRect:
                    state = index.data(Qt.CheckStateRole)
                    state = Qt.Unchecked if state == Qt.Checked else Qt.Checked
                    self.model().setData(index, state, Qt.CheckStateRole)
                self.clickedData = None
            return True
        elif (
                event.type() == event.MouseButtonPress
                and event.button() == Qt.LeftButton
        ):
            index = self.treeView.indexAt(event.pos())
            state = index.data(Qt.CheckStateRole)
            state = Qt.Unchecked if state == Qt.Checked else Qt.Checked
            self.model().setData(index, state, Qt.CheckStateRole)
            self.treeView.viewport().update()
            self.clickedData = None
            return True
        return super().eventFilter(obj, event)

    def showPopup(self):
        self.treeView.expandAll()
        width = self.treeView.sizeHintForColumn(0)
        maxCount = self.maxVisibleItems()
        index = self.model().index(0, 0, self.rootModelIndex())
        visible = 0
        while index.isValid():
            visible += 1
            index = self.treeView.indexBelow(index)
            if visible > maxCount:
                width += self.treeView.verticalScrollBar().sizeHint().width()
                break
        self.treeView.setMinimumWidth(width)
        super().showPopup()

    def RightClickMenu(self):
        self.menu.clear()
        self.ascending_action = QAction('Ascending',self)
        self.menu.addAction(self.ascending_action)
        self.ascending_action.triggered.connect(self.ascending_order)

        self.descending_action = QAction('Descending')
        self.descending_action.triggered.connect(self.descending_order)
        self.menu.addAction(self.descending_action)

        self.original_action = QAction('Original Order')
        self.original_action.triggered.connect(self.original_order)
        self.menu.addAction(self.original_action)

        self.reverse_action = QAction('Reverse order')
        self.reverse_action.triggered.connect(self.reverse_order)
        self.menu.addAction(self.reverse_action)

    def cntxt_menu(self,pos):
        self.model_01 = self.model()
        self.menu.exec_(self.mapToGlobal(pos))

    def ascending_order(self):
        self.model_01.setSortRole(Qt.DisplayRole)
        self.model_01.sort(0,Qt.AscendingOrder)

    def descending_order(self):
        self.model_01.setSortRole(Qt.DisplayRole)
        self.model_01.sort(0, Qt.DescendingOrder)

    def original_order(self):
        self.model_01.setSortRole(Qt.UserRole)
        self.model_01.sort(0,Qt.AscendingOrder)

    def reverse_order(self):
        self.model_01.setSortRole(Qt.UserRole)
        self.model_01.sort(0, Qt.DescendingOrder)

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("QCombobox")
        self.comboBox = MyCombo()
        self.comboBox.setEditable(False)
        self.model = MyModel()
        self.comboBox.setModel(self.model)

        self.vbox = QVBoxLayout()
        self.setLayout(self.vbox)
        self.vbox.addWidget(self.comboBox)

if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    ex = MainWindow()
    ex.show()
    sys.exit(app.exec_())

内容的提问来源于stack exchange,提问作者Rithik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 11:31:34