Introduction

This page arose out of a very frustrating few days spent on trying to specify a derived class in an XRC document. Specifically, what I wanted to do was to create my own subclass of wx.ListCtrl, note in the XRC document that it should use this derived class, and have it be automatically instantiated for me.

For the purposes of this example, the subclass is very simple. The only way it differs from the wx.ListCtrl class is that at instantiation time it adds a single column to itself. This subclass-specific code is contained in the _PostInit() method (the name _PostInit seems to be a mild convention for such code in TwoStageCreation classes).

The Solution

Here's what finally worked.

   1 # MyList.py
   2 import wx, wx.xrc
   3 
   4 gui_xrc = """<?xml version="1.0" encoding="ISO-8859-1"?>
   5     <resource version="2.3.0.1">
   6         <object class="wxFrame" name="frame">
   7             <style>wxDEFAULT_FRAME_STYLE</style>
   8             <title>List Test</title>
   9             <object class="wxBoxSizer">
  10                 <orient>wxVERTICAL</orient>
  11                 <object class="sizeritem">
  12                     <option>1</option>
  13                     <flag>wxEXPAND</flag>
  14                     <!-- This here is the problematic area: -->
  15                     <object class="wxListCtrl" name="list" subclass="MyList.cl">
  16                         <style>wxLC_REPORT|wxSUNKEN_BORDER</style>
  17                     </object>
  18                 </object>
  19             </object>
  20         </object>
  21     </resource>"""
  22 
  23 class MyApp(wx.App):
  24     def OnInit(self):
  25         self.res = wx.xrc.EmptyXmlResource()
  26         self.res.LoadFromString(gui_xrc)
  27         self.frame = self.res.LoadFrame(None, 'frame')
  28         self.list = wx.xrc.XRCCTRL(self.frame, 'list')
  29         self.frame.Show()
  30         return True
  31 
  32 class cl(wx.ListCtrl):
  33     _firstEventType = wx.EVT_SIZE
  34     #_firstEventType = wx.EVT_WINDOW_CREATE
  35 
  36     def __init__(self):
  37         p = wx.PreListCtrl()
  38         # the Create step is done by XRC.
  39         self.PostCreate(p)
  40 
  41         # Apparently the official way to do this is:
  42         #self.Bind(wx.EVT_WINDOW_CREATE, self.OnCreate)
  43         # But this seems to be the actually working way, cf:
  44         # http://aspn.activestate.com/ASPN/Mail/Message/wxPython-users/2169189
  45         self.Bind(self._firstEventType, self.OnCreate)
  46 
  47     def _PostInit(self):
  48         self.InsertColumn(0, "Test")
  49 
  50     def OnCreate(self, evt):
  51         self.Unbind(self._firstEventType)
  52         # Called at window creation time
  53         self._PostInit()
  54         self.Refresh()
  55 
  56 if __name__ == '__main__':
  57     app = MyApp()
  58     app.MainLoop()

Notes

The main frustration comes from the fact that instantiation of objects from XRC files uses TwoStageCreation. If you were just creating the objects directly in Python code, the usual form of constructor would work:

   1        def __init__(self, parent, ID, size, style):
   2            wx.ListCtrl.__init__(self, parent, ID, size, style)
   3            # Derived-class specific initialization

...but since the object is being loaded from XRC, it is instantiated with no arguments. The XRC component will then call Create when the object is placed.

wx.EVT_WINDOW_CREATE

The official solution seems to be to bind a handler to wx.EVT_WINDOW_CREATE. This event, in theory, is passed at the time the window is placed and ready to go.

However, I couldn't seem to get this to work. For some reason the event never seemed to arrive, so the _PostInit() method of my derived class was never called. (I'm not sure why this is and would be grateful if someone could tell me.) And yet the XmlSubclass example in the wxPython 2.5.3.1 demo binds to wx.EVT_WINDOW_CREATE and it seems to work fine.

At any rate, I've used this trick from the wxPython-users list instead. The first event the window gets is a wx.EVT_SIZE event, so at instantiation time we bind to that, and then when we get it, we immediately unbind from the size event (otherwise we'd be initializing every time the window was resized) and call _PostInit().

In Hindsight

The actual ListCtrl I was working on was more complex than this, but sometime after I finally got it working I began to doubt whether I needed to subclass ListCtrl at all. What I was really looking for was a much higher-level interface to a ListCtrl, so that I could do things like myListCtrlInstance.AddMyComplexObjectAsASingleRow(complexObject).

With that in mind, my new plan is to use a plain-vanilla wx.ListCtrl as a component in a higher-level object (probably called ListManager or something).

   1 class myApp(wx.App):
   2     def OnInit(self):
   3         # [ ... ]
   4         vanilla_list = wx.xrc.XRCCTRL(self.frame, 'list')
   5         self.listmgr = ListManager(vanilla_list, ...)
   6 
   7     def OnHandleSomeGoofyAction(self, evt):
   8         cplx = self.DoSomeRidiculousTransaction()
   9         self.listmgr.AddRow(cplx)

SubclassingListCtrlWithXrc (last edited 2008-03-11 10:50:20 by localhost)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.