PySide6 上的自定义文件浏览器实现

问题描述

我想在 PySide6 上实现文件浏览器,我的目标是:

  1. 显示文件文件夹,.. 始终位于顶部(无论排序如何),以便用户可以双击它并上一级。
  2. .. 之后,无论排序如何,我都希望显示文件夹和文件(就像 Windows 资源管理器一样)。
  3. 具有显示特定文件集的替代显示模式(它们可以位于不同的驱动器、不同的文件夹等)。

我目前正在使用以下代码来初始化模型和视图:

self.model = QFileSystemModel()
self.model.setRootPath(path)
self.model.setFilter(QDir.Nodot | QDir.AllEntries)
self.model.sort(0,Qt.sortOrder.AscendingOrder)
self.ui.treeView.setModel(self.model)
self.ui.treeView.setRootIndex(self.model.index(path))
self.ui.treeView.header().setSortIndicator(0,Qt.AscendingOrder)
self.ui.treeView.setSortingEnabled(True)

我实际上使用的是带有附加列的自定义 QFileSystemModel,而不是 QFileSystemModel()。

我遇到的问题是:

  • .. 与其他内容一起排序,不会出现在顶部
  • 排序后目录不会停留在顶部

我不明白我正在解决的问题的最佳方法是什么。

我看到以下选项:

  • 使用 QSortFilterProxyModel 并以某种方式强制 .. 始终在最前面,无论排序(不确定是否可能),并首先保留目录(有一个 related question),我也可以将它用于上面的第 3 点按特定条件显示文件
  • 使用完全不同的方法,可能是 qfilesystemwatcher 或 QTreeWidget,我将手动填写(保持 .. 始终在最前面似乎在任何情况下都会引起麻烦)。
  • 在加载或排序后以某种方式在 QTreeView 顶部添加 ..

我尝试实现 QSortFilterProxyModel,但遇到了另一个问题:我不明白我应该如何修改 treeView.setRootIndex() 调用

所以我的具体问题是:

  1. 我可以使用 QSortFilterProxyModel 来解决上述所有问题吗?如果是,请提供示例实现。
  2. 如果您认为有更好的方法解决这个问题,请描述它。

解决方法

    import PySide2
    from PySide2 import QtWidgets
    from PySide2.QtWidgets import QFileSystemModel
    from PySide2.QtWidgets import QMainWindow,QWidget
    from PySide2.QtWidgets import QTreeView
    from PySide2.QtWidgets import QVBoxLayout
    from PySide2.QtCore import QDir
    from PySide2.QtCore import QSortFilterProxyModel
    from PySide2.QtCore import Qt

    class View(QMainWindow):
        def __init__(self):
            super().__init__()

            self._w_main = QWidget()
            self.setCentralWidget(self._w_main)
            self.tree_view = QTreeView(self._w_main)

            self._layout = QVBoxLayout()
            self._layout.addWidget(self.tree_view)
            self._w_main.setLayout(self._layout)

    class SortingModel(QSortFilterProxyModel):
        def lessThan(
                self,source_left: PySide2.QtCore.QModelIndex,source_right: PySide2.QtCore.QModelIndex
        ):
            file_info1 = self.sourceModel().fileInfo(source_left)
            file_info2 = self.sourceModel().fileInfo(source_right)

            if file_info1.isDir() and file_info2.isDir():
                return super().lessThan(source_left,source_right)
            return file_info1.isDir()

    app = QtWidgets.QApplication([])
    view = View()
    model = QFileSystemModel()
    model.setRootPath('.')
    model.setFilter(QDir.NoDot | QDir.AllEntries)
    model.sort(0,Qt.SortOrder.AscendingOrder)
    sorting_model = SortingModel()
    sorting_model.setSourceModel(model)
    view.tree_view.setModel(sorting_model)
    view.tree_view.setRootIndex(sorting_model.mapFromSource(model.index('.')))
    view.tree_view.header().setSortIndicator(0,Qt.AscendingOrder)
    view.tree_view.setSortingEnabled(True)
    view.showMaximized()
    return sys.exit(app.exec_())

QSortFilterProxyModel 在我的机器上的 .. PySide2 中默认将 5.14.1 放在列表的顶部。

mapFromSource 用于 setRootIndex

的索引映射 ,

以下解决方案有效:

class SortingModel(QSortFilterProxyModel):
    def lessThan(self,source_left: QModelIndex,source_right: QModelIndex):
        file_info1 = self.sourceModel().fileInfo(source_left)
        file_info2 = self.sourceModel().fileInfo(source_right)       
        
        if file_info1.fileName() == "..":
            return self.sortOrder() == Qt.SortOrder.AscendingOrder

        if file_info2.fileName() == "..":
            return self.sortOrder() == Qt.SortOrder.DescendingOrder
                
        if (file_info1.isDir() and file_info2.isDir()) or (file_info1.isFile() and file_info2.isFile()):
            return super().lessThan(source_left,source_right)

        return file_info1.isDir() and self.sortOrder() == Qt.SortOrder.AscendingOrder

初始化视图和模型的代码与@bartolo-otrit 答案中的相同:

    model = QFileSystemModel()
    model.setRootPath('.')
    model.setFilter(QDir.NoDot | QDir.AllEntries)
    model.sort(0,Qt.AscendingOrder)
    view.tree_view.setSortingEnabled(True)