wx.grid单元格图片无法加载问题求助(宝可梦卡牌查看器开发)
问题排查与修复
核心原因
你遇到的问题是自定义GridCellRenderer的Draw函数未被调用,根源在于:当通过SetTable设置自定义GridTableBase(即你的CardTableSource)后,Grid会优先使用GridTableBase.GetAttr()返回的单元格属性。而你的CardTableSource.GetAttr()每次都返回一个全新的默认GridCellAttr,这会覆盖你之前通过SetCellRenderer为单个单元格设置的自定义渲染器,导致Grid从未使用你的CardImageRenderer。
修复方案
修改CardTableSource,让它针对图片列(索引1)返回带有自定义渲染器的GridCellAttr,同时将图片数据存储在TableSource中以保证生命周期(避免Bitmap被垃圾回收)。
修改后的完整代码
import wx import wx.grid as wxGrid import pandas as pd class MainApp(wx.App): def OnInit(self): MainFrame().Show(True) return True class MainFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, title='Pokemon Cards', size=(1080, 720)) self.Centre() self.SetBackgroundColour('Green') mainSizer = wx.BoxSizer(wx.VERTICAL) tableBasePanel = wx.Panel(self) tableBasePanel.SetBackgroundColour('Yellow') tableSizer = wx.BoxSizer(wx.HORIZONTAL) self.SetSizer(mainSizer) mainSizer.Add(tableBasePanel, 1, wx.ALL | wx.EXPAND, 5) tableBasePanel.SetSizer(tableSizer) tableSizer.Add(CardTable(tableBasePanel)) class CardTable(wxGrid.Grid): def __init__(self, parent): wxGrid.Grid.__init__(self, parent) self.parent = parent self.data = pd.read_csv('Card Database.csv', na_filter=False) self.LoadRows(0, 20) def LoadRows(self, start, end): # 提取图片列数据并加载Bitmap df_slice = self.data.iloc[start:end] # 存储图片Bitmap到列表,传递给TableSource self.card_images = [] for img_name in df_slice.iloc[:, 1]: img_path = f'Card Images/{img_name}' self.card_images.append(wx.Bitmap(img_path, wx.BITMAP_TYPE_ANY)) # 初始化TableSource并传入图片列表 table_source = CardTableSource(df_slice, self.card_images) self.SetTable(table_source, takeOwnership=True) # 原有设置保留 self.HideRowLabels() self.DisableDragColSize() self.DisableCellEditControl() self.AutoSizeColumns() self.SetColSize(1, 200) eff1Col = self.data.columns.get_loc('Effect 1') for iCol in range(eff1Col, eff1Col + 7): self.SetColSize(iCol, 300) # 移除原有的SetCellRenderer循环,改为由TableSource管理 for iRow in range(end-start): self.SetRowSize(iRow, 280) # 清空图片列的文本值(可选,因为渲染器会覆盖显示) self.SetCellValue(iRow, 1, '') class CardImageRenderer(wxGrid.GridCellRenderer): def __init__(self, img): wxGrid.GridCellRenderer.__init__(self) self.img = img def Draw(self, grid, attr, dc, rect, row, col, isSelected): # 绘制单元格背景 dc.SetBackgroundMode(wx.SOLID) if isSelected: dc.SetBrush(wx.Brush(wx.BLUE, wx.SOLID)) dc.SetPen(wx.Pen(wx.BLUE, 1, wx.SOLID)) else: dc.SetBrush(wx.Brush(wx.WHITE, wx.SOLID)) dc.SetPen(wx.Pen(wx.WHITE, 1, wx.SOLID)) dc.DrawRectangle(rect) # 绘制图片(适配单元格大小,避免超出) if self.img.IsOk(): img_width, img_height = self.img.GetSize() # 计算居中位置 x = rect.x + (rect.width - img_width) // 2 y = rect.y + (rect.height - img_height) // 2 dc.DrawBitmap(self.img, x, y, True) # 必须重写Clone方法,wxGrid需要复制渲染器实例 def Clone(self): return CardImageRenderer(self.img) class CardTableSource(wxGrid.GridTableBase): def __init__(self, data=None, card_images=None): wxGrid.GridTableBase.__init__(self) self.headerRows = 1 self.data = data if data is not None else pd.DataFrame() self.card_images = card_images if card_images is not None else [] def GetNumberRows(self): return self.data.shape[0] def GetNumberCols(self): return self.data.shape[1] def GetValue(self, row, col): return self.data.iloc[row, col] def SetValue(self, row, col, value): self.data.iat[row, col] = value def GetColLabelValue(self, col): return str(self.data.columns[col]) def GetTypeName(self, row, col): return wxGrid.GRID_VALUE_STRING def GetAttr(self, row, col, prop): attr = wxGrid.GridCellAttr() # 针对图片列(索引1)设置自定义渲染器 if col == 1 and row < len(self.card_images): attr.SetRenderer(CardImageRenderer(self.card_images[row])) return attr MainApp().MainLoop()
关键修改点说明
- CardTableSource新增图片存储:在初始化时接收
card_images列表,保存每个行对应的卡牌图片Bitmap,确保图片不会被垃圾回收。 - 重写GetAttr方法:判断当前列是否为图片列,若是则返回带有
CardImageRenderer的GridCellAttr,让Grid正确使用自定义渲染器。 - CardImageRenderer添加Clone方法:wxGrid在渲染单元格时会克隆渲染器实例,必须实现此方法才能正常工作(原代码缺少这一步,也是潜在问题)。
- 优化图片绘制逻辑:改为居中绘制图片,适配单元格大小,避免图片超出边界。
- 移除原SetCellRenderer循环:不再单独给每个单元格设置渲染器,统一由TableSource管理,避免被默认Attr覆盖。
内容的提问来源于stack exchange,提问作者MarcoTalin12
相关产品推荐
相关产品推荐

