Custom Derived Classes for wxPython XRC resources

First of all, this isn’t a topic that is bran new or which requires new documentation. I have learned a few quirks about the process for creating custom controls, panels, frames, and other elements with XRC files in wxPython and I thought I’d write up a little post.

Let me point you to the two most important resources. The wxPython wiki has pretty much all you need to now to get started. First is the TwoStageCreation process that the XRC implementation uses to create classes. The second is the UsingXmlResources page.
There is also the XRCTutorial. But you should be past that if you’re trying to create custom controls.

Here is a summary of the basic process. First, create your XRC file. Here is a simple sample I generated with DialogBlocks.

wxtest.xrc:



    
        
        400,300
        wxTest
        
            
            
                wxHORIZONTAL
                
                    wxALIGN_CENTER_VERTICAL|wxALL
                    5
                    
                    5,5
                
                
                    wxALIGN_CENTER_VERTICAL|wxALL
                    5
                    
                        
                    
                
                
                    wxALIGN_CENTER_VERTICAL|wxALL
                    5
                    
                    5,5
                
            
        
    

Notice at the top of the file, the wxFrame object has the attribute subclass=”wxtest.MyFrame”. This tells XRC that when instantiating this class, use my subclass of wxFrame instead of wxFrame itself.

Here is the python code to load the frame:

wxtest.py:


import wx
from wx import xrc

import logging as log
log.basicConfig ( format='%(message)s', level=log.DEBUG )


class MyFrame(wx.Frame):
    
    def __init__(self):
        log.debug ( "__init__")
        f=wx.PreFrame()
        self.PostCreate(f)
        self.Bind( wx.EVT_WINDOW_CREATE , self.OnCreate)
    def OnCreate(self,evt):
        log.debug ( "OnCreate" )
        self.Unbind ( wx.EVT_WINDOW_CREATE )
        wx.CallAfter(self.__PostInit)
        evt.Skip()
        return True
    def __PostInit(self):
        log.debug ( "__PostInit" )
        self.Bind ( wx.EVT_BUTTON, self.OnButton, id=xrc.XRCID ( "ID_BUTTON" ) )
    def OnButton(self,evt):
        log.debug ( "Button Pressed" )
        
        
if __name__ == '__main__':
    app=wx.PySimpleApp(redirect=False)
    res=xrc.XmlResource ( 'wxtest.xrc' )
    frame=res.LoadFrame( None, "ID_WXFRAME" )
    frame.Show()
    app.MainLoop()

You might wonder why bind the OnCreate method to the WINDOW_CREATE event instead of simply doing your initialization in the init method. The answer is that 1) the window isn’t really all the way created at the __init__ stage and 2), it doesn’t get all the way created until after the events start being processed. Why use wx.CallAfter and a __PostInit method then? This seems to not be absolutely necessary if you’re creating a frame or panel as part of your main program. If you are creating an XRC control withing an event handler, e.g., you have a button that is used to create a dialog that is defined in an XRC file, the control still isn’t all the way loaded until after the Create Event. I’m not sure why you can’t just use wx.CallAfter in your __init__. I tried that and found it to work, but the documented procedure is to use both OnCreate and PostInit.

Important summary note: Child controls obtained with xrc.XRCCTRL aren’t available to be loaded until the __PostInit method.

Now, having the basic example up and running, it’s time to add a little customization. I found it annoying to write the __init__, OnCreate, and __PostInit methods for each of my custom controls. You can create a parent class to do this.

custxrc.py:

class XrcControl:
    def __init__(self):
        log.debug ( "__init__")
        self.Bind( wx.EVT_WINDOW_CREATE , self.OnCreate)
    def OnCreate(self,evt):
        log.debug ( "OnCreate" )
        self.Unbind ( wx.EVT_WINDOW_CREATE )
        wx.CallAfter(self._PostInit)
        evt.Skip()
        return True
    def _PostInit(self):
        raise RuntimeError ( "Extend this method." )

class XrcFrame(wx.Frame, XrcControl):    
    def __init__(self):        
        f=wx.PreFrame()
        self.PostCreate(f)
        XrcControl.__init__(self)       

It’s pretty easy to see how you could create Xrc elements for Panels and other widgets. (I think Python needs templates.) Anyway, MyFrame is now a bit smaller:

class MyFrame(XrcFrame):
    def __init__(self):
        XrcFrame.__init__(self)
    def _PostInit(self):
        log.debug ( "__PostInit" )
        self.Bind ( wx.EVT_BUTTON, self.OnButton, id=xrc.XRCID ( "ID_BUTTON" ) )
    def OnButton(self,evt):
        log.debug ( "Button Pressed" )

Summary:

* You can’t access child elements until OnCreate for controls that are loaded outside of an event handler and PostInit (call it whatever you want) for controls that are loaded from within an event handler.
* You can use Python’s very flexible style to put lots of the code for creating XRC controls into base classes.

This entry was posted in Programming and tagged , , , , , , , . Bookmark the permalink.