PyQt5中QFileSystemModel切换过滤器异常及代理模型适配问题咨询
QTreeView + QFileSystemModel 切换文件/文件夹过滤的解决方案
问题分析
你遇到的是QFileSystemModel动态切换过滤器时的视图残留问题,核心原因:
- QFileSystemModel内部缓存机制会保留已加载的目录项,切换过滤器后未触发视图完全刷新
- 选中状态的项会被视图特殊处理,即便清除选择也可能因模型缓存导致残留
不新建模型的解决方案
无需每次切换都重建模型,通过以下步骤即可解决问题:
方案1:直接操作QFileSystemModel
切换过滤器前先重置视图根索引为无效状态,设置新过滤器后再重新绑定根索引,强制视图重新加载当前目录内容,清除缓存残留:
from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * class Model(QWidget): def __init__(self): super().__init__() self.model = QFileSystemModel() self.model.setFilter(QDir.Dirs | QDir.NoDotAndDotDot | QDir.AllDirs) self.model.setRootPath(QDir.rootPath()) self.view = QTreeView(uniformRowHeights=True, itemsExpandable=False, rootIsDecorated=False) self.view.setModel(self.model) self.current_root = QDir.currentPath() self.view.setRootIndex(self.model.index(self.current_root)) grid = QGridLayout(self) grid.addWidget(self.view, 0, 0, 1, 2) self.folder_radio = QRadioButton('Folders', checked=True) self.folder_radio.toggled.connect(self.changefilter) grid.addWidget(self.folder_radio, 1, 0, Qt.AlignRight) self.file_radio = QRadioButton('Files') grid.addWidget(self.file_radio, 1, 1) def changefilter(self, checked): self.view.clearSelection() self.view.setCurrentIndex(QModelIndex()) # 先重置根索引为无效,清除视图缓存 self.view.setRootIndex(QModelIndex()) if self.file_radio.isChecked(): self.model.setFilter(QDir.Files | QDir.NoDotAndDotDot | QDir.AllDirs) else: self.model.setFilter(QDir.Dirs | QDir.NoDotAndDotDot | QDir.AllDirs) # 重新绑定根索引,强制视图加载当前目录内容 self.view.setRootIndex(self.model.index(self.current_root))
方案2:使用QSortFilterProxyModel实现精准过滤
不要通过QFileSystemModel.setFilter直接过滤,而是让模型加载所有内容,通过代理模型的自定义逻辑控制显示项,既保证目录结构正常,又能实现严格的文件/文件夹切换:
class ModelWithProxy(QWidget): def __init__(self): super().__init__() self.model = QFileSystemModel() # 模型始终保留AllDirs,确保目录结构正常加载 self.model.setFilter(QDir.AllEntries | QDir.NoDotAndDotDot) self.model.setRootPath(QDir.rootPath()) self.proxy = QSortFilterProxyModel() self.proxy.setSourceModel(self.model) self.view = QTreeView(uniformRowHeights=True, itemsExpandable=False, rootIsDecorated=False) self.view.setModel(self.proxy) self.current_root = QDir.currentPath() self.view.setRootIndex(self.proxy.mapFromSource(self.model.index(self.current_root))) grid = QGridLayout(self) grid.addWidget(self.view, 0, 0, 1, 2) self.folder_radio = QRadioButton('Folders', checked=True) self.folder_radio.toggled.connect(self.changefilter) grid.addWidget(self.folder_radio, 1, 0, Qt.AlignRight) self.file_radio = QRadioButton('Files') grid.addWidget(self.file_radio, 1, 1) self.show_folders = True self.changefilter(True) def changefilter(self, checked): self.show_folders = self.folder_radio.isChecked() # 强制代理模型重新执行过滤逻辑 self.proxy.invalidateFilter() def filterAcceptsRow(self, source_row, source_parent): # 重写代理模型的过滤方法,仅控制当前根目录下的项 index = self.model.index(source_row, 0, source_parent) if not index.isValid(): return False # 父目录允许通过,保证目录结构正常遍历 if not self.model.filePath(index).startswith(self.current_root + '/'): return True # 根据当前模式显示文件夹或文件 if self.show_folders: return self.model.isDir(index) else: return not self.model.isDir(index)
关键说明
- 直接操作模型时,重置根索引是核心,能彻底清除视图缓存的残留项
- 使用代理模型时,模型必须保留
QDir.AllDirs以保证目录结构正常,过滤逻辑完全交给代理模型实现
内容的提问来源于stack exchange,提问作者alec
相关产品推荐
相关产品推荐

