PyQt6与PyQt5中QTreeView/QFileSystemModel排序行为差异:Bug还是特性变更?
PyQt6中QFileSystemModel子类修改列数据后排序失效的问题解决
问题描述
我开发了一个基于QTreeView和QFileSystemModel子类的应用,子类重写data()方法将第2列(原文件类型/日期列)替换为文件扩展名。在PyQt5中点击该列排序时,会按照返回的扩展名排序;但PyQt6中却依然按照底层的日期类型排序,子类返回的数据被忽略。这是PyQt6的Bug还是特性变更?如何实现预期的排序效果?
测试环境:Linux Mint 22.1,PyQt 5.15、PyQt 6.6.1,修改代码第5行的布尔值即可切换版本测试。
复现代码:
#!/usr/bin/env python3 import sys, os if False: #True: from PyQt5 import QtWidgets, QtCore from PyQt5.QtWidgets import QFileSystemModel else: from PyQt6 import QtWidgets, QtCore from PyQt6.QtGui import QFileSystemModel class FileSystemModel(QFileSystemModel): def __init__(self, parent=None): super().__init__(parent=parent) def data(self, idx, role): if idx.column() == 2 and \ (role == QtCore.Qt.ItemDataRole.DisplayRole or role == QtCore.Qt.ItemDataRole.EditRole): parIndex = idx.parent() col2Index = self.index(idx.row(), 2, parIndex) col2Data = super().data(col2Index, QtCore.Qt.ItemDataRole.DisplayRole) col0Index = self.index(idx.row(), 0, parIndex) col0Data = super().data(col0Index, QtCore.Qt.ItemDataRole.DisplayRole) _,ext = os.path.splitext(col0Data) return ext else: return super().data(idx, role) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) tvw = QtWidgets.QTreeView() tvw.setSortingEnabled(True) tvw.setAlternatingRowColors(True) tvw.setItemsExpandable(True) tvw.setColumnWidth(0, 300) tvw.setColumnWidth(1, 75) tvw.setColumnWidth(2, 75) def sz(): return QtCore.QSize(600,600) tvw.sizeHint = sz root = '/' test = '/etc' mdl = FileSystemModel(parent=tvw) fltr = (QtCore.QDir.Filter.AllDirs | QtCore.QDir.Filter.NoDotAndDotDot | QtCore.QDir.Filter.Files) mdl.setFilter(fltr) mdl.setRootPath(root) tvw.setModel(mdl) tvw.setRootIndex(mdl.index(test)) dlg = QtWidgets.QDialog() vbx = QtWidgets.QVBoxLayout() vbx.addWidget(tvw) dlg.setLayout(vbx) dlg.exec() print('\nFINI') sys.exit()
原因分析
这不是PyQt6的Bug,而是Qt 6对模型排序逻辑的有意变更。在Qt 5中,视图排序默认使用DisplayRole的数据进行比较;但Qt 6中,QFileSystemModel的列会绑定特定角色用于排序,比如第2列默认使用UserRole + 1(对应文件修改日期),此时仅重写data()返回DisplayRole的数据不会影响排序逻辑,因为排序依赖的是其他角色的数据。
解决方案
要让排序使用自定义的数据,需要重写QFileSystemModel的lessThan()方法——这是模型用于排序比较的核心方法,针对第2列使用自定义的扩展名进行比较即可。
修改后的子类代码:
class FileSystemModel(QFileSystemModel): def __init__(self, parent=None): super().__init__(parent=parent) def data(self, idx, role): if idx.column() == 2 and \ (role == QtCore.Qt.ItemDataRole.DisplayRole or role == QtCore.Qt.ItemDataRole.EditRole): col0Index = self.index(idx.row(), 0, idx.parent()) col0Data = super().data(col0Index, QtCore.Qt.ItemDataRole.DisplayRole) _, ext = os.path.splitext(col0Data) return ext else: return super().data(idx, role) def lessThan(self, left, right): # 针对第2列,用自定义的扩展名做比较 if left.column() == 2 and right.column() == 2: left_name = super().data(self.index(left.row(), 0, left.parent()), QtCore.Qt.ItemDataRole.DisplayRole) right_name = super().data(self.index(right.row(), 0, right.parent()), QtCore.Qt.ItemDataRole.DisplayRole) _, left_ext = os.path.splitext(left_name) _, right_ext = os.path.splitext(right_name) return left_ext < right_ext # 其他列保留原有排序逻辑 return super().lessThan(left, right)
说明
- 重写
lessThan()后,第2列排序时会提取左右项的文件名扩展名做字符串比较,实现预期的排序效果。 - 其他列的排序逻辑和原生
QFileSystemModel保持一致,不影响原有功能。
内容的提问来源于stack exchange,提问作者dave
相关产品推荐
相关产品推荐

