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

如何为经多轮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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 11:11:33