如何为经多轮QSortFilterProxy处理的QTableView实现分页?
PyQt6 链式代理后实现正确分页的解决方案
问题背景
在PyQt6中使用包含18000行数据的自定义QTableView,数据经过多个自定义QSortFilterProxy链式处理后,尝试用基于源模型行号的PaginationProxy实现分页时,放在代理链末尾会出现异常,仅显示少量预期结果。核心原因是当前分页代理的filterAcceptsRow方法判断的是源模型的行号,而非经过前面代理过滤后的行号,导致分页逻辑完全错误。
方案一:修复分页代理实现
正确的分页代理需要基于前面代理过滤后的总行数计算页码范围,而非直接使用源模型行号。我们可以通过重写rowCount、index、data方法,直接控制当前页显示的行,同时提供分页控制的方法:
class PaginationProxy(QtCore.QSortFilterProxyModel): def __init__(self, maxrows=30, parent=None): super().__init__(parent) self.maxrows = maxrows self.current_page = 1 # 起始页码设为1 def set_current_page(self, page): # 确保页码在有效范围内 total_pages = self.total_pages() page = max(1, min(page, total_pages)) if self.current_page != page: self.current_page = page self.layoutChanged.emit() # 通知视图更新数据 def total_pages(self): # 基于前面代理过滤后的总行数计算总页码 total_rows = self.sourceModel().rowCount() return (total_rows + self.maxrows - 1) // self.maxrows # 向上取整 def rowCount(self, parent=QtCore.QModelIndex()): if parent.isValid(): return 0 total_rows = self.sourceModel().rowCount() start_row = (self.current_page - 1) * self.maxrows end_row = min(start_row + self.maxrows, total_rows) return end_row - start_row def index(self, row, column, parent=QtCore.QModelIndex()): if not self.hasIndex(row, column, parent): return QtCore.QModelIndex() # 将当前页的行号映射到源模型(前面代理)的对应行号 source_row = (self.current_page - 1) * self.maxrows + row source_index = self.sourceModel().index(source_row, column, parent) return self.createIndex(row, column, source_index.internalPointer()) def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole): if not index.isValid(): return None # 映射到源模型的索引获取数据 source_row = (self.current_page - 1) * self.maxrows + index.row() source_index = self.sourceModel().index(source_row, index.column()) return self.sourceModel().data(source_index, role) # 封装分页控制方法 def first_page(self): self.set_current_page(1) def prev_page(self): self.set_current_page(self.current_page - 1) def next_page(self): self.set_current_page(self.current_page + 1) def last_page(self): self.set_current_page(self.total_pages())
使用方式
代理链保持原有结构,新增的分页代理直接放在链末尾,通过调用分页控制方法实现跳转:
self.model = CustomTableModel(dataTable, self.labels) self.proxyModelA = TextFilterProxy(0) self.proxyModelA.setSourceModel(self.model) self.proxyModelB = DictValueProxy(5, "Name") self.proxyModelB.setSourceModel(self.proxyModelA) self.paginateProxy = PaginationProxy(maxrows=30) self.paginateProxy.setSourceModel(self.proxyModelB) self.tableView.setModel(self.paginateProxy) # 分页控制示例 self.paginateProxy.set_current_page(3) # 跳转到第3页 self.paginateProxy.prev_page() # 上一页 self.paginateProxy.next_page() # 下一页 self.paginateProxy.first_page() # 首页 self.paginateProxy.last_page() # 末页
方案二:不依赖额外代理的实现方式
如果不想新增代理,可以直接在视图层面结合已有代理模型处理分页:
- 维护当前页码和每页行数,通过已有代理模型的
rowCount获取过滤后的总行数,计算页码范围 - 切换页面时,计算当前页起始行,通过代理模型的
index获取对应行,调用tableView.scrollTo定位 - 但这种方式需要手动隐藏非当前页的行,效率和代码简洁度不如代理方案,仅适合简单场景
关键说明
- 原分页代理的错误核心:混淆了源模型行号与代理过滤后的行号,
QSortFilterProxy的filterAcceptsRow参数row_num是源模型的行号,不能直接用于判断当前页范围 - 修改后的代理完全基于前面过滤后的模型数据计算分页,符合Qt MVC架构,当前面代理的过滤结果变化时,总页码会自动重新计算,视图也会同步更新
内容的提问来源于stack exchange,提问作者The Onion Ninja
相关产品推荐
相关产品推荐

