WxPython documentation is somewhat lacking, theres a lot of obscure
parts of the API that the docs dont elaborate on enough, no fleshed out
samples in the demo folder of the sources (nor the separate
samples folder)[1(1)] and no quantity of
googlefu to save you. One of these nooks in WxPython is the
DataView family of classes. In this document is some Tips,
Tricks and Pitfalls ive learned while dealing with the bloody things
ObjectToItem
uses the id functionWhen using PyDataViewModel they use the bulletin
id function, meaning your inputted objects for
ItemToObject/ObjectToItem, need to be the same
instance, defining __eq__ or whatever other pythonic™
abstraction wont cut it. From what I believe using id() is
considered black magic wizardry in python land, so theres that solace i
guess. Instead simply override the
ItemToObject/ObjectToItem functions with
def ItemToObject(self, item):
# Replace item.GetID() with whatever object hashing you want to use
return self.Mapper[int(item.GetID())]
def ObjectToItem(self, obj: data.Categories.CategoryRef):
self.Mapper[obj.CatID] = obj
return wxd.DataViewItem(obj.CatID)(Debugging this stuff can be a nightmare!)
Get* FunctionsIn a DataViewModel WxPython will excessively call all
Get overridden functions inside it, especially when
resizing. If you are doing database lookups within those functions or
something, then you’ll have a very slow experience,
even for python standards! Simply wrap all your intensive
Get functions as well as IsContainer with the
functools.cache decorator, though remember
not to wrap you Set functions too, at first it works…until
it doesn’t functools.cache is a highly underrated feature
of python, as well as memorization in general, ill likely do another one
of these when Im more versed in it.
Despite being in the docs2, you (i) will have
probably missed it, you can enable container columns by overriding
HasContainerColumns in your DateViewModel.
Additionally there is also IsCompatibleVariantType3, which should allow types other than
string to be returned from a custom renderer. However too much of my
code base serializes and deserializes to strings for me to bother with,
so thats homework for the those following at home i guess.
DataViewItem Ids close to the onscreen
presentationIn an ideal world, your ids will be a 1:1 mapping of whats on screen, with for example each Id being the next row starting from 0. Alas things are never perfect.
DataViewCustomRendererI was about to tell you that rendering a plain old WxPython control
inside of a DataViewCustomRenderer was impossible, again no
docs or samples on this matter, plus the “““stateless”“” nature of
Render makes it quite hard to implement stateful controls.
However I spent an afternoon night figuring it out and came
back with this rather janky solution. Ive only tested this on wxGTK+
(with a custom theme lul), so here be dragons. Additionally you may need
to modify the offsets I used to get it centered in the cell
class _ChoiceRenderer(wxd.DataViewCustomRenderer):
def __init__(self, parent, listctrl, choices):
wxd.DataViewCustomRenderer.__init__(self)
self.listctrl = listctrl # Could replace with self.GetView,
# but segfaulted last time I tried
self.parent = parent
self.Choices = choices
self.ControlNameList = []
def GetValue(self):
print(self.CurElm)
return str(self.value)
def GetSize(self):
return wx.Size(200, 30)
def Render(self, cell:wx.Rect, dc:wx.DC, state):
self.CellHeight = cell.height
if not hasattr(self,str(cell.y)):
TmpR = wx.Choice(self.parent, choices=self.Choices)
TmpR.__setattr__("Row", cell.Position.y // self.CellHeight)
TmpR.Bind(wx.EVT_CHOICE, self.OnChoice)
self.__setattr__(str(cell.y), TmpR)
self.ControlNameList.append(str(cell.y))
self.CurElm:wx.Choice = self.__getattribute__(str(cell.y))
self.CurElm.Show()
self.CurElm.SetSelection(self.value)
self.CurElm.Position = wx.Point(cell.Position.x+1,
cell.Position.y+self.listctrl.Position.y+cell.height-3)
self.CurElm.SetSize(cell.width, cell.height)
return True
def OnChoice(self,evt):
print("ONCHOICE", evt.GetEventObject().Row)
# It doesn't seem possible to call back into the rest of
# DataViewModel here, call into you underlying API i guess?
def UnRender(self):
for e in self.ControlNameList:
self.__getattribute__(e).Hide()
def HasEditorCtrl(self):
return False
# In your business code
self.Bind(wxd.EVT_DATAVIEW_ITEM_COLLAPSED, self.OnCollapsed)
def OnCollapsed(self, evt):
<YourChoiceRenderer>.UnRender()