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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 11:42:12