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

在QTreeView与QFileSystemModel中添加虚拟父目录实现返回上级功能

实现带虚拟父目录的文件浏览器

核心实现思路

由于虚拟Parent项仅为UI层面需求,无需修改底层文件系统模型,子类化QTreeView是最优方案。核心逻辑包括:

  • 在根节点前插入虚拟Parent项,通过重写视图的行计数方法预留位置
  • 重写视图的数据渲染、索引校验方法,让虚拟项能正常显示和交互
  • 处理Parent项的双击事件,切换回当前根目录的上级目录
  • 自定义窗口标题,显示当前文件夹的名称和完整路径

完整实现代码

import sys
from typing import Union

from PySide6.QtCore import QModelIndex, QDir, QSortFilterProxyModel, Qt
from PySide6.QtWidgets import QApplication, QTreeView, QFileSystemModel


class CustomProxyModel(QSortFilterProxyModel):
    NAME_COLUMN = 0

    def filterAcceptsColumn(self, source_column: int, source_parent: Union[QModelIndex, QPersistentModelIndex]) -> bool:
        return source_column == self.NAME_COLUMN

    def isDir(self, proxy_index: QModelIndex):
        source_index = self.mapToSource(proxy_index)
        source_model = self.sourceModel()

        if source_model and source_index.isValid():
            return source_model.isDir(source_index)
        else:
            return False

    def get_parent_path(self, proxy_index: QModelIndex) -> str:
        """获取当前根目录的上级路径"""
        source_index = self.mapToSource(proxy_index)
        source_model = self.sourceModel()
        if source_model and source_index.isValid():
            parent_path = QDir(source_model.filePath(source_index)).absolutePath() + "/.."
            return parent_path
        return ""


class CustomTreeView(QTreeView):
    PARENT_ROW = 0

    def __init__(self, parent=None):
        super().__init__(parent)
        self.header().hide()

    def rowCount(self, parent=QModelIndex()) -> int:
        """根节点额外添加一行用于显示Parent项"""
        if not parent.isValid():
            return super().rowCount(parent) + 1
        return super().rowCount(parent)

    def indexAt(self, pos):
        """调整坐标,让Parent项能被正确点击"""
        index = super().indexAt(pos)
        if not index.isValid():
            rect = self.visualRect(self.model().index(0, 0))
            if rect.contains(pos):
                return self.createIndex(self.PARENT_ROW, 0)
        return index

    def isIndexValid(self, index: QModelIndex) -> bool:
        """标记Parent项为有效索引"""
        if not index.isValid():
            return False
        if not index.parent().isValid() and index.row() == self.PARENT_ROW:
            return True
        return super().isIndexValid(index)

    def data(self, index: QModelIndex, role: int = Qt.DisplayRole):
        """返回Parent项的显示数据"""
        if not index.parent().isValid() and index.row() == self.PARENT_ROW:
            if role == Qt.DisplayRole:
                return "Parent"
            elif role == Qt.DecorationRole:
                # 使用系统默认文件夹图标作为Parent项图标
                return self.style().standardIcon(self.style().SP_DirIcon)
        # 根节点下的真实文件/文件夹,行号需减1对应原模型数据
        elif not index.parent().isValid():
            adjusted_index = self.model().index(index.row() - 1, index.column())
            return self.model().data(adjusted_index, role)
        return super().data(index, role)

    def mouseDoubleClickEvent(self, event):
        """处理双击事件:双击Parent返回上级,双击文件夹设为根目录"""
        index = self.indexAt(event.pos())
        if not index.isValid():
            super().mouseDoubleClickEvent(event)
            return

        if not index.parent().isValid() and index.row() == self.PARENT_ROW:
            # 双击Parent项,切换到上级目录
            current_root = self.rootIndex()
            proxy_model = self.model()
            parent_path = proxy_model.get_parent_path(current_root)
            if parent_path:
                source_model = proxy_model.sourceModel()
                source_parent_index = source_model.index(parent_path)
                proxy_parent_index = proxy_model.mapFromSource(source_parent_index)
                self.setRootIndex(proxy_parent_index)
                self.update_window_title()
        else:
            # 处理真实文件夹的双击
            adjusted_index = index
            if not index.parent().isValid():
                adjusted_index = self.model().index(index.row() - 1, index.column())
            if self.model().isDir(adjusted_index):
                self.setRootIndex(adjusted_index)
                self.update_window_title()
        super().mouseDoubleClickEvent(event)

    def update_window_title(self):
        """更新窗口标题,显示当前文件夹名称和路径"""
        current_root = self.rootIndex()
        proxy_model = self.model()
        source_index = proxy_model.mapToSource(current_root)
        source_model = proxy_model.sourceModel()
        if source_model and source_index.isValid():
            dir_name = QDir(source_model.filePath(source_index)).dirName()
            full_path = source_model.filePath(source_index)
            self.setWindowTitle(f"{dir_name} ({full_path})")


if __name__ == '__main__':
    app = QApplication(sys.argv)

    tree = CustomTreeView()

    path = QDir.rootPath() + 'Users/'

    filesystem_model = QFileSystemModel()
    filesystem_model.setReadOnly(True)
    filesystem_model.setRootPath(path)

    proxy = CustomProxyModel()
    proxy.setSourceModel(filesystem_model)

    source_index = filesystem_model.index(path, 0)
    initial_root = proxy.mapFromSource(source_index)
    tree.setModel(proxy)
    tree.setRootIndex(initial_root)
    tree.update_window_title()

    tree.show()
    sys.exit(app.exec())

关键逻辑说明

  1. CustomTreeView类

    • rowCount:给根节点行数+1,为Parent项预留显示位置
    • data:专门处理Parent项的文本和图标渲染,真实项的行号需要减1来匹配原模型数据
    • mouseDoubleClickEvent:区分双击对象是虚拟Parent还是真实文件夹,分别执行返回上级或切换根目录的操作
    • update_window_title:根据当前根目录的路径动态更新窗口标题
  2. CustomProxyModel扩展

    • 添加get_parent_path方法,用于获取当前根目录的上级路径,为视图切换目录提供数据支持

内容的提问来源于stack exchange,提问作者Jeroen De Geeter

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 19:20:22