如何为wx.grid.Grid实现分页?解决百万行CSV加载卡顿问题
解决方案:wx.grid.Grid 百万行CSV分页加载
问题背景
需要加载10列、100万行的CSV文件到wx.grid.Grid,直接全量加载并自动调整行高耗时超1小时,因此需要实现分页功能。以下提供两种思路的可运行代码,以及其他可行方案。
方案一:滚动触底自动追加行
原代码的wx.EVT_SCROLL_BOTTOM触发逻辑不够可靠,改用监听滚动窗口事件,判断滚动位置是否接近底部来触发追加。同时模拟CSV数据加载(实际使用时替换为读取CSV的逻辑):
import wx import wx.grid import random class MyForm(wx.Frame): def __init__(self): wx.Frame.__init__(self, parent=None, title="滚动触底分页Grid") panel = wx.Panel(self) self.myGrid = MyGrid(panel) self.myGrid.init_grid() sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.myGrid, 1, wx.EXPAND) panel.SetSizerAndFit(sizer) self.Maximize() class MyGrid(wx.grid.Grid): def __init__(self, parent): wx.grid.Grid.__init__(self, parent) self.total_rows = 1000000 # 总数据行数 self.page_size = 1000 # 每页行数 self.current_rows = 0 # 当前已加载行数 def init_grid(self): # 初始化列 self.CreateGrid(0, 10) for col in range(10): self.SetColLabelValue(col, f"Column{col+1}") self.SetDefaultColSize(width=150, resizeExistingCols=True) self.SetDefaultRowSize(height=30, resizeExistingRows=True) # 加载第一页 self.load_page() # 绑定滚动事件 self.Bind(wx.EVT_SCROLLWIN, self.on_scroll) def load_page(self): # 模拟加载CSV数据,实际使用时替换为pd.read_csv或csv模块读取指定行 if self.current_rows >= self.total_rows: return rows_to_add = min(self.page_size, self.total_rows - self.current_rows) self.AppendRows(rows_to_add) for row in range(self.current_rows, self.current_rows + rows_to_add): for col in range(10): self.SetCellValue(row, col, f"Row{row+1}_Col{col+1}_{random.randint(1,100)}") self.current_rows += rows_to_add def on_scroll(self, event): # 获取滚动条信息 scroll_pos = self.GetScrollPos(wx.VERTICAL) scroll_range = self.GetScrollRange(wx.VERTICAL) scroll_thumb = self.GetScrollThumb(wx.VERTICAL) # 当滚动到距离底部小于100行时,加载下一页 if scroll_pos + scroll_thumb >= scroll_range - 100: self.load_page() event.Skip() if __name__ == "__main__": app = wx.App(False) frame = MyForm().Show() app.MainLoop()
方案二:按钮控制分页(Prev/Next)
通过按钮切换分页,每次清空当前Grid并加载对应页的数据,适合需要精准切换页码的场景:
import wx import wx.grid import random class MyForm(wx.Frame): def __init__(self): wx.Frame.__init__(self, parent=None, title="按钮分页Grid") panel = wx.Panel(self) self.page_num = 1 # 当前页码 self.page_size = 1000 # 每页行数 self.total_rows = 1000000 # 总数据行数 self.total_pages = (self.total_rows + self.page_size - 1) // self.page_size # 总页数 # 分页按钮 btn_sizer = wx.BoxSizer(wx.HORIZONTAL) self.prev_btn = wx.Button(panel, label="上一页") self.next_btn = wx.Button(panel, label="下一页") self.page_label = wx.StaticText(panel, label=f"页码:{self.page_num}/{self.total_pages}") btn_sizer.Add(self.prev_btn, 0, wx.ALL, 5) btn_sizer.Add(self.page_label, 0, wx.ALL | wx.CENTER, 5) btn_sizer.Add(self.next_btn, 0, wx.ALL, 5) # Grid组件 self.myGrid = wx.grid.Grid(panel) self.init_grid() self.load_current_page() # 绑定按钮事件 self.prev_btn.Bind(wx.EVT_BUTTON, self.on_prev) self.next_btn.Bind(wx.EVT_BUTTON, self.on_next) # 整体布局 main_sizer = wx.BoxSizer(wx.VERTICAL) main_sizer.Add(btn_sizer, 0, wx.CENTER) main_sizer.Add(self.myGrid, 1, wx.EXPAND) panel.SetSizerAndFit(main_sizer) self.Maximize() # 初始化按钮状态 self.update_btn_state() def init_grid(self): self.myGrid.CreateGrid(0, 10) for col in range(10): self.myGrid.SetColLabelValue(col, f"Column{col+1}") self.myGrid.SetDefaultColSize(width=150, resizeExistingCols=True) self.myGrid.SetDefaultRowSize(height=30, resizeExistingRows=True) def load_current_page(self): # 清空现有行 if self.myGrid.GetNumberRows() > 0: self.myGrid.DeleteRows(0, self.myGrid.GetNumberRows()) # 计算当前页的起始和结束行 start_row = (self.page_num - 1) * self.page_size end_row = min(start_row + self.page_size, self.total_rows) rows_to_load = end_row - start_row self.myGrid.AppendRows(rows_to_load) # 模拟加载CSV数据,实际替换为读取指定范围的行 for row_idx in range(rows_to_load): actual_row = start_row + row_idx for col in range(10): self.myGrid.SetCellValue(row_idx, col, f"Row{actual_row+1}_Col{col+1}_{random.randint(1,100)}") self.page_label.SetLabel(f"页码:{self.page_num}/{self.total_pages}") def on_prev(self, event): if self.page_num > 1: self.page_num -= 1 self.load_current_page() self.update_btn_state() def on_next(self, event): if self.page_num < self.total_pages: self.page_num += 1 self.load_current_page() self.update_btn_state() def update_btn_state(self): self.prev_btn.Enable(self.page_num > 1) self.next_btn.Enable(self.page_num < self.total_pages) if __name__ == "__main__": app = wx.App(False) frame = MyForm().Show() app.MainLoop()
其他可行分页方案
- 虚拟Grid(Virtual Grid):使用
wx.grid.PyGridTableBase实现虚拟表格,只渲染当前可见的行,不需要提前加载所有数据。当滚动时,动态获取并渲染可见范围内的数据,性能最优,适合超大数据量。 - 下拉加载更多:类似滚动触底,但在Grid底部添加一个"加载更多"的提示行,点击后追加下一页数据,比自动加载更可控。
- 页码输入框:在按钮分页的基础上添加页码输入框,支持直接跳转到指定页码,提升操作效率。
内容的提问来源于stack exchange,提问作者tarun_b
相关产品推荐
相关产品推荐

