QSortFilterProxyModel实现QTreeView的过滤的缺点和改进

本文介绍如何使用QSortFilterProxyModel对TreeView进行关键字过滤,并针对原有方法导致符合条件的子节点被过滤的问题提出改进方案,确保正确显示所有匹配项。

    一般如果需要对一个视图进行关键字过滤,我们会使用QT的类QSortFilterProxyModel,将视图(view)的模型设置为这个代理类,将代理类的源模型定义为原始模型,然后定义代理模型的filterKeyColumn, filterRole, filterFixString或filterRegExp等属性后就可以实现过滤功能。如:

MyTreeModel model;
QSortFilterProxyModel sfmodel;
MyView view;

view.setModel(&sfmodel);
sfmodel.setSourceModel(&model);
sfmodel.setFilterKeyColumn(0);	// optional
sfmodel.setFilterRole(Qt::DisplayRole); // default, optional
sfmodel.setFilterFixedString("keyword");
sfmodel.setFilterRegExp("[hH]ello");

    但是对于TreeView和TreeModel,这样的做法有些缺陷,如果在子节点上的数据根据过滤规则可以显示,但它的parent节点根据过滤规则是不显示的。这样以来会因为父节点被过滤掉,而导致符合条件的子节点也一起被过滤掉了。如果需要对这个缺陷进行改进,可以这样来做:从QSortFilterProxyModel继承一个自己的代理类,重新实现它的filterAcceptsRow函数,当节点不是一个叶子节点(在末端,没有子节点)时不应用过滤规则,只对叶子节点应用过滤规则,这样就可以达到上述要求。代码如下:

// \reimp
bool MyFilterProxyModel::filterAcceptsRow ( int source_row,
                            const QModelIndex & source_parent ) const
{
    QModelIndex source_index = sourceModel()->index(source_row, 0, source_parent);
    if (sourceModel()->rowCount(source_index)>0)
        return true;
    else
        return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
}    

    上面的代码会带来一个新的缺陷,就是对于非叶子节点不应用过滤规则,对于那些没有子节点符合过滤规则,且自身节点也不符合过滤规则的节点,也会一直在视图中显示。如果需要弥补这个问题,可以继续做以下的改进:在应用过滤规则时,如果自身不符合过滤规则,再去检查所有的子节点(递归调用至所有的后代节点),若所有的子节点都不符合过滤规则,那么这个子节点就不显示,只要有一个后代节点符合规则,就需要显示这个节点。代码如下:

// \reimp
bool MyFilterProxyModel::filterAcceptsRow ( int source_row,
                            const QModelIndex & source_parent ) const
{
    bool filter = QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);

    if (filter)
    {
        return true;
    }
    else
    {
        // check all decendant's
        QModelIndex source_index = sourceModel()->index(source_row, 0, source_parent);
        for (int k=0; k<sourceModel()->rowCount(source_index); k++)
        {
            if (filterAcceptsRow(k, source_index))
            {
                return true;
            }
        }
    }

    return false;
}
    按照这样的做法,就可以达到一个比较理想的过滤效果。

<think>我们使用QSortFilterProxyModel时,当源模型(source model)的数据发生变化,我们希望代理模型(proxy model)能及时更新,从而刷新QTreeView的显示。以下是几种常见情况及其解决方法: 1. **源模型数据变化(增删改)**:代理模型会自动更新视图,因为QSortFilterProxyModel会监听源模型的信号。 2. **过滤条件变化**:需要重新设置过滤条件(如调用setFilterRegExp)或调用invalidateFilter()方法强制代理模型重新过滤。 3. **排序条件变化**:调用sort方法重新排序。 ### 关键方法 - **invalidate()**:使当前缓存失效,代理模型会重新从源模型获取数据(但不会重新排序或过滤)。 - **invalidateFilter()**:使过滤缓存失效,重新应用过滤条件。 - **invalidateRowsCache()** **invalidateColumnsCache()**:使行或列缓存失效(较少使用)。 - **setFilterRegExp()**:设置新的过滤正则表达式,同时会触发重新过滤。 - **sort(column, order)**:重新排序。 ### 示例场景 假设我们有一个QFileSystemModel作为源模型,一个QSortFilterProxyModel作为代理模型,以及一个QTreeView显示代理模型。 ```python import sys from PyQt5.QtWidgets import (QApplication, QTreeView, QFileSystemModel, QVBoxLayout, QWidget, QPushButton, QLineEdit, QMainWindow) from PyQt5.QtCore import QSortFilterProxyModel, QDir, Qt class FileBrowser(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("文件浏览器") self.resize(800, 600) # 创建主部件 central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) # 创建文件系统模型 self.source_model = QFileSystemModel() self.source_model.setRootPath(QDir.rootPath()) # 创建代理模型 self.proxy_model = QSortFilterProxyModel() self.proxy_model.setSourceModel(self.source_model) self.proxy_model.setDynamicSortFilter(True) # 动态排序过滤 # 创建树视图并设置代理模型 self.tree_view = QTreeView() self.tree_view.setModel(self.proxy_model) self.tree_view.setRootIndex(self.proxy_model.mapFromSource(self.source_model.index(QDir.homePath()))) # 创建过滤输入框 self.filter_edit = QLineEdit() self.filter_edit.setPlaceholderText("输入过滤文本...") self.filter_edit.textChanged.connect(self.on_filter_text_changed) # 创建刷新按钮(用于演示手动刷新) self.refresh_button = QPushButton("刷新") self.refresh_button.clicked.connect(self.refresh_view) layout.addWidget(self.filter_edit) layout.addWidget(self.tree_view) layout.addWidget(self.refresh_button) def on_filter_text_changed(self, text): """当过滤文本变化时,更新代理模型的过滤条件""" # 设置过滤的正则表达式,这里使用通配符模式(也可以是正则表达式) self.proxy_model.setFilterWildcard(text) # 也可以使用:self.proxy_model.setFilterRegExp(text) # 注意:设置过滤条件后,代理模型会自动重新过滤,不需要手动调用invalidateFilter() def refresh_view(self): """手动刷新视图(例如源模型有外部修改,但代理模型未自动更新时)""" # 方法1:使代理模型的过滤缓存失效(重新过滤) self.proxy_model.invalidateFilter() # 方法2:如果排序条件变化,可以重新排序 # self.proxy_model.sort(0, Qt.AscendingOrder) # 方法3:如果源模型数据变化未触发代理模型更新,可以重置代理模型(但会重置所有状态,如展开状态) # self.proxy_model.setSourceModel(self.source_model) # 不推荐,因为会重置视图状态 if __name__ == "__main__": app = QApplication(sys.argv) window = FileBrowser() window.show() sys.exit(app.exec_()) ``` ### 解释 1. **过滤条件变化**:当用户在QLineEdit中输入文本时,`textChanged`信号触发`on_filter_text_changed`,通过`setFilterWildcard`更新过滤条件,代理模型会自动重新过滤并更新视图。 2. **手动刷新**:点击“刷新”按钮会调用`invalidateFilter()`,强制代理模型重新应用过滤条件。这在源模型数据变化但代理模型没有自动更新时可能有用(但通常代理模型会自动响应源模型的变化)。 ### 注意事项 - **动态排序过滤**:通过`setDynamicSortFilter(True)`(默认是False)可以让代理模型在源模型数据变化时自动重新排序过滤。如果设置为False,则需要手动调用`invalidateFilter()`或`invalidate()`来更新。 - **源模型变化**:如果源模型是自定义模型,确保在数据变化时发出正确的信号(如`dataChanged`、`rowsInserted`等),这样代理模型才能自动更新。 ### 常见问题解决 - **视图未更新**:如果源模型数据变化后视图没有更新,首先检查源模型是否发出了正确的信号。如果代理模型设置了动态排序过滤,通常会自动更新。如果仍然不更新,尝试调用`invalidateFilter()`。 - **性能问题**:对于大型模型,频繁的重新过滤排序可能导致性能问题。可以通过设置过滤条件时使用延时(如QTimer)来减少操作频率。 ### 高级用法:自定义过滤 如果需要自定义过滤逻辑,可以继承`QSortFilterProxyModel`并重写`filterAcceptsRow`方法。 ```python class CustomProxyModel(QSortFilterProxyModel): def filterAcceptsRow(self, source_row, source_parent): # 获取源模型的索引 index = self.sourceModel().index(source_row, 0, source_parent) # 获取文件名 file_name = self.sourceModel().fileName(index) # 自定义过滤条件:例如,只接受大于10KB的文件 file_size = self.sourceModel().size(index) return file_name.contains(self.filterRegExp()) and file_size > 10240 # 使用自定义代理模型 self.proxy_model = CustomProxyModel() self.proxy_model.setSourceModel(self.source_model) ``` 在自定义过滤逻辑中,如果过滤条件变化(如文件大小阈值变化),需要调用`invalidateFilter()`来刷新。 ### 总结 - 当过滤条件变化时,使用`setFilterRegExp`(或类似方法)更新过滤条件,代理模型会自动刷新。 - 当需要强制刷新时,调用`invalidateFilter()`(重新过滤)或`sort`(重新排序)。 - 确保源模型发出正确信号,代理模型才能自动更新。 通过以上方法,可以有效地更新QTreeView的数据展示。 §§相关问题§§ 1. 如何在QSortFilterProxyModel实现自定义排序? 2. 如何提高QSortFilterProxyModel在大数据量下的性能? 3. 如何同时使用多个过滤条件? 4. 如何保存恢复QTreeView的展开状态?</think># PyQt5 QSortFilterProxyModel 刷新 QTreeView 数据指南 在 PyQt5 中使用 `QSortFilterProxyModel` 时刷新 `QTreeView` 的数据展示,需要理解代理模型的工作机制以及如何正确触发数据更新。以下是完整的解决方案最佳实践: ## 核心刷新机制 ### 1. 源模型更新时自动刷新 当源模型数据变化时,代理模型会自动更新视图(前提是源模型正确发出信号): ```python # 源模型数据变化示例 source_model = QStandardItemModel() proxy_model = QSortFilterProxyModel() proxy_model.setSourceModel(source_model) tree_view.setModel(proxy_model) # 添加新项目 - 视图会自动更新 new_item = QStandardItem("新项目") source_model.appendRow(new_item) ``` ### 2. 手动刷新代理模型 当过滤条件变化时需要手动刷新: ```python # 设置新的过滤条件后刷新 proxy_model.setFilterRegExp("新条件") proxy_model.invalidateFilter() # 关键刷新方法 ``` ## 完整实现示例 ```python import sys from PyQt5.QtWidgets import (QApplication, QTreeView, QStandardItemModel, QSortFilterProxyModel, QVBoxLayout, QWidget, QPushButton, QLineEdit, QMainWindow) from PyQt5.QtCore import Qt, QRegExp class TreeViewUpdater(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("代理模型刷新示例") self.resize(600, 400) # 主部件布局 central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout(central_widget) # 1. 创建源模型 self.source_model = QStandardItemModel() self._populate_model() # 2. 创建代理模型 self.proxy_model = QSortFilterProxyModel() self.proxy_model.setSourceModel(self.source_model) self.proxy_model.setDynamicSortFilter(True) # 启用动态更新 # 3. 创建树视图 self.tree_view = QTreeView() self.tree_view.setModel(self.proxy_model) self.tree_view.setSortingEnabled(True) # 启用排序 # 4. 过滤控制 self.filter_input = QLineEdit() self.filter_input.setPlaceholderText("输入过滤文本...") self.filter_input.textChanged.connect(self.update_filter) # 5. 操作按钮 btn_layout = QVBoxLayout() self.add_btn = QPushButton("添加新项目") self.add_btn.clicked.connect(self.add_item) self.update_btn = QPushButton("强制刷新视图") self.update_btn.clicked.connect(self.force_refresh) # 添加到布局 layout.addWidget(self.filter_input) layout.addWidget(self.tree_view) layout.addWidget(self.add_btn) layout.addWidget(self.update_btn) def _populate_model(self): """初始化源模型数据""" for i in range(5): parent = QStandardItem(f"父项目 {i+1}") for j in range(3): child = QStandardItem(f"子项目 {j+1}") parent.appendRow(child) self.source_model.appendRow(parent) def update_filter(self, text): """更新过滤条件""" self.proxy_model.setFilterRegExp(QRegExp(text, Qt.CaseInsensitive)) self.proxy_model.invalidateFilter() # 触发过滤刷新 def add_item(self): """添加新项目到源模型""" new_parent = QStandardItem(f"新父项目 {self.source_model.rowCount()+1}") new_child = QStandardItem("新子项目") new_parent.appendRow(new_child) self.source_model.appendRow(new_parent) # 自动展开新项目 proxy_index = self.proxy_model.mapFromSource(new_parent.index()) self.tree_view.expand(proxy_index) def force_refresh(self): """强制刷新整个代理模型""" # 方法1:使过滤缓存失效 self.proxy_model.invalidate() # 方法2:重置代理模型(更彻底但成本更高) # self.proxy_model.setSourceModel(self.source_model) # 方法3:触发布局变化信号 # self.proxy_model.layoutChanged.emit() if __name__ == "__main__": app = QApplication(sys.argv) window = TreeViewUpdater() window.show() sys.exit(app.exec_()) ``` ## 刷新方法详解 ### 1. `invalidateFilter()` 当过滤条件变化时使用,仅重新应用过滤规则: ```python self.proxy_model.setFilterRegExp("新条件") self.proxy_model.invalidateFilter() # 高效刷新过滤结果 ``` ### 2. `invalidate()` 使整个代理模型缓存失效,重新处理所有数据: ```python self.proxy_model.invalidate() # 完全刷新代理模型 ``` ### 3. `setSourceModel()` 重置源模型(最彻底的刷新方式): ```python # 保存当前源模型 current_source = self.proxy_model.sourceModel() # 重置源模型 self.proxy_model.setSourceModel(None) self.proxy_model.setSourceModel(current_source) ``` ### 4. 布局变化信号 通知视图布局已改变: ```python self.proxy_model.layoutAboutToBeChanged.emit() self.proxy_model.layoutChanged.emit() ``` ## 性能优化技巧 ### 1. 批量更新时暂停刷新 ```python # 开始批量更新 self.proxy_model.layoutAboutToBeChanged.emit() # 执行多个数据更改操作 for i in range(100): self.source_model.appendRow(QStandardItem(f"项目 {i}")) # 结束批量更新 self.proxy_model.layoutChanged.emit() ``` ### 2. 使用延迟刷新 ```python from PyQt5.QtCore import QTimer def update_filter(self, text): # 取消之前的定时器 if hasattr(self, '_filter_timer'): self._filter_timer.stop() # 设置新的定时器(300ms后刷新) self._filter_timer = QTimer.singleShot(300, lambda: self.proxy_model.setFilterRegExp(text)) ``` ### 3. 动态排序过滤 ```python # 启用动态更新(默认False) self.proxy_model.setDynamicSortFilter(True) ``` ## 常见问题解决 ### 问题1:添加项目后视图不更新 **解决方案**: - 确保源模型正确发出`rowsInserted`信号 - 检查代理模型是否设置了`setDynamicSortFilter(True)` - 手动调用`invalidate()`或`invalidateFilter()` ### 问题2:过滤后项目位置不正确 **解决方案**: ```python # 刷新后滚动到正确位置 def ensure_visible(self, index): proxy_index = self.proxy_model.mapFromSource(index) self.tree_view.scrollTo(proxy_index) ``` ### 问题3:自定义过滤后刷新无效 **自定义代理模型示例**: ```python class CustomProxyModel(QSortFilterProxyModel): def filterAcceptsRow(self, source_row, source_parent): # 自定义过滤逻辑 index = self.sourceModel().index(source_row, 0, source_parent) return index.data().startswith("A") def update_custom_filter(self): self.invalidateFilter() # 自定义过滤后调用 ``` ## 最佳实践总结 1. **小规模更新**:使用`invalidateFilter()`仅刷新过滤结果 2. **大规模更新**:使用`invalidate()`刷新整个代理模型 3. **结构变化**:使用`layoutChanged`信号 4. **批量操作**:使用`layoutAboutToBeChanged``layoutChanged`包裹 5. **用户输入**:添加延迟刷新优化性能 通过正确使用这些方法,可以确保 `QSortFilterProxyModel` `QTreeView` 的数据展示始终保持最新状态[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值