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
相关产品推荐
相关产品推荐

