Reputation: 59333
I'm making a Windows application using wx.grid.Grid
that will handle very large Microsoft Excel documents. Currently it opens a file with 17 columns and 12 000+ rows quite quickly, and I can scroll around smoothly. (This is a virtual table that operates on data from a custom table class.)
Anyway, the problem starts when I use custom grid cell attribute objects, e.g:
grid.SetAttr(row, col, SomeGridCellAttr('#FF0000'))
Once every cell in a row has a custom grid cell attribute, the performance of the grid just goes 99.9% into the toilet. Instead of smooth scrolling around I'm lucky if I get 1 redraw per 3 seconds. I fixed most of these instances by using grid.SetColAttr
instead, which restored performance to it's previous smoothness, but there is one case where that won't work. The application iterates over every cell in a column (12 000 cells), performs some processing on the data and applies a custom grid cell attribute based on the results. Once that is done, the grid becomes a sluggish nightmare to work with.
Is there any way of removing this gruesome performance hit and retain the custom cell attributes? I suspect the answer is quite simple to somebody who knows the inner workings of the grid and it's cell attributes.
Thanks.
Upvotes: 0
Views: 1972
Reputation: 6226
Setting a cell attribute will add a new GridCellAttr
to the list of the GridCellAttrProvider
.
As the list grows, looking up a specific attribute for a cell (by iterating through the list and comparing the coordinates) gets slower and slower.
You can try to speed it up by implementing your own PyGridTableBase.SetAttr
and GetAttr
(using a dict for example):
EDIT: Updated code to allow overwriting attributes and emulate the default implementations attribute ownership.
class MyTable(wx.grid.PyGridTableBase):
atts = {}
def Hash(self,row,col):
#FIXME: assumes a constant number of rows and rows > cols
return col + row * self.GetNumberRows()
def SetAttr(self,attr,row,col):
HASH = self.Hash(row, col)
if HASH in self.atts:
# decrement usage count of existing attr
self.atts[HASH].DecRef()
#assign new attribute
self.atts[HASH] = attr
def GetAttr(self,row,col,kind):
HASH = self.Hash(row, col)
if HASH in self.atts:
attr = self.atts[HASH]
attr.IncRef() # increment reference count
return attr
return None
To allow setting entire rows and columns, you'll also have to implement:
def SetRowAttr(self,attr,row):
for col in range(self.GetNumberCols()):
attr.IncRef() # increment reference count for SetAttr
self.SetAttr(attr,row,col)
attr.DecRef() # attr passed to SetRowAttr no longer needed
def SetColAttr(self,attr,col):
for row in range(self.GetNumberRows()):
attr.IncRef()
self.SetAttr(attr,row,col)
attr.DecRef()
NOTE: when passing a GridCellAttr
to Set*Attr()
, the default implementation will take ownership of the attribute.
To re-use the same attribute (stored in a class variable for example), you have to either Clone()
it or increment its usage count (IncRef()
)
before passing it to a Set*Attr()
method (cloning may increase memory consumption).
The above example lacks proper removal of attributes: SetAttr()
could check for a None attr and decrement the ref count at the coordinates specified, then delete the entry from the dict.
SetCol/RowAttr()
can be optimized by adding dicts for row and col, similar to SetAttr()
. GetAttr()
could then check for an existing entry in the row and col dict and merge/override the attribute(s) with the one from the cell dict (that's the principle used by the default implementation). For proper cleanup of the dict(s), call DecRef
on every entry before .clear()
.
Alternatively, you could derive from wx.grid.GridCellAttrProvider
and attach it with PyGridTableBase.SetAttrProvider()
. This would prevent direct access to the table, however.
Upvotes: 3