在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())
关键逻辑说明
CustomTreeView类
rowCount:给根节点行数+1,为Parent项预留显示位置data:专门处理Parent项的文本和图标渲染,真实项的行号需要减1来匹配原模型数据mouseDoubleClickEvent:区分双击对象是虚拟Parent还是真实文件夹,分别执行返回上级或切换根目录的操作update_window_title:根据当前根目录的路径动态更新窗口标题
CustomProxyModel扩展
- 添加
get_parent_path方法,用于获取当前根目录的上级路径,为视图切换目录提供数据支持
- 添加
内容的提问来源于stack exchange,提问作者Jeroen De Geeter
相关产品推荐
相关产品推荐

