wxPython: An XRCed Tutorial

If you're new to wxPython but not new to XML, you might find this article useful to you. Why? Because wxPython supports XRC, an XML file format that describes the GUI in XML, duh. In fact, wxPython's Documentation & Demos package includes an editor just for creating and manipulating these files that is called, XRCed. This article will take you on a journey to see XRCed's features and general usage.

One confusing aspect of XRCed is that it used to be a project separate from wxPython and its website still exists here. I've been told that the old version from that website works really well with screen readers compared to the new version that is shipped with the demo package. So if you have sight problems, you might find that version more suitable. Of course, the old version hasn't been updated since 2007...so pick your poison.

Getting Started

xrced_main.png

Once you have the Demo application installed, run the tool called XRC Resource Editor. You should see something like the above. It's a two screen interface with a main screen on the left and a widget screen on the right. To get started, we should make a simple application!

Creating Our First Application

Let's create a simple two button application with XRCed. It won't do anything, but it will show you how to make a quick GUI. Open XRCed and in the widget window (below) click on the wxFrame button.

You should see an unnamed wxFrame appear in the right application as a root in a tree widget (see screenshot at beginning of the section). For this example, we're going to give the frame a name of "MainFrame". Now with the frame selected in the tree, add a panel named "MainPanel". Next, in the second floating screen, there's a row of buttons along the top. Click the fourth from the left, the one that looks like several red rectangles and then choose the BoxSizer one (make sure that the panel object is highlighted in the other screen first though).

Now with the box sizer tree item selected, click on the floating window's third button and add two buttons to the tree, naming them as shown. Save your work and you should end up with a file that looks like this:



  
    
      
        
          
            
          
        
        
          
            
          
        
        wxHORIZONTAL
      
    
  

It's shocking, but XRCed actually produces easy-to-read XML code. Now we just need to figure out how to load the XML with wxPython. Fortunately, it's actually quite easy. Check this out:

import wx
from wx import xrc

class MyApp(wx.App):
    def OnInit(self):
        self.res = xrc.XmlResource("twoBtns.xrc")
        
        self.frame = self.res.LoadFrame(None, 'MainFrame')
        
        self.frame.Show()
        return True
        
if __name__ == "__main__":
    app = MyApp(False)
    app.MainLoop()

To load the XML, we need to import the xrc module from wx. Then we load the XML with the following line: xrc.XmlResource("twoBtns.xrc"). Note that we had to pass in the name (or path) of the xrc file. You'll probably need to change it to whatever you called your copy. Then to load the frame, we call the xml resource object's LoadFrame method, passing it None (i.e. no parent) and the name that we gave the frame in the xrc file. This is where it's really easy to make a mistake. You HAVE to type the name of widget in the Python code exactly the same way that you did in the xrc file or it will not work (or it might work, but not in the way you expect). Yes, the name is case sensitive. Anyway, once that's done, you just do what you normally do in a wxPython file.

Creating Something More Complex

The example in the previous section is pretty bare-bones. Let's take a look at how we can create part of the application in XRC and part of it in wxPython. In the screenshot above, we have a notebook with two pages and a PlateButton underneath it. The notebook, frame and panel are all made in XRC whereas the PlateButton is just normal wx. Here's the XML:



wxVERTICAL

wxALL|wxEXPAND
5

XRC Notebook Demo

Now let's add the PlateButton:

import wx
from wx import xrc
import wx.lib.platebtn as platebtn

class MyApp(wx.App):
    def OnInit(self):
        self.res = xrc.XmlResource("notebook2.xrc")
        
        frame = self.res.LoadFrame(None, 'DemoFrame')
        panel = xrc.XRCCTRL(frame, "DemoPanel")
        notebook = xrc.XRCCTRL(panel, "DemoNotebook")
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        btn = platebtn.PlateButton(panel, label="Test", 
                                   style=platebtn.PB_STYLE_DEFAULT)
        btn.Bind(wx.EVT_BUTTON, self.onButton)
        sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5)
        sizer.Add(btn)
        panel.SetSizer(sizer)
        
        frame.Show()
        return True
    
    #----------------------------------------------------------------------
    def onButton(self, event):
        """"""
        print "You pressed the button!"
        
if __name__ == "__main__":
    app = MyApp(False)
    app.MainLoop()

As you can see, that was as simple as it is to create the application in just plain wxPython. If there had been a wx.Button defined in XRC, we would do the same thing that we did for the panel to create a handle of it. Once we had the handle, we could bind events to the button as we would normally.

Using XRCed to Generate Python Code

The XRCed application includes a Python code generator that we can subclass for our own code. To start, we'll use the first simple example in this article and then we'll expand that example and show you how to bind events. In XRCed, load the first example and then go to File, Generate Python. Accept the defaults and click the Generate module button. You should now have some auto-generated code that looks like this:

# This file was automatically generated by pywxrc.
# -*- coding: UTF-8 -*-

import wx
import wx.xrc as xrc

__res = None

def get_resources():
    """ This function provides access to the XML resources in this module."""
    global __res
    if __res == None:
        __init_resources()
    return __res




class xrcMainFrame(wx.Frame):
#!XRCED:begin-block:xrcMainFrame.PreCreate
    def PreCreate(self, pre):
        """ This function is called during the class's initialization.
        
        Override it for custom setup before the window is created usually to
        set additional window styles using SetWindowStyle() and SetExtraStyle().
        """
        pass
        
#!XRCED:end-block:xrcMainFrame.PreCreate

    def __init__(self, parent):
        # Two stage creation (see http://wiki.wxpython.org/index.cgi/TwoStageCreation)
        pre = wx.PreFrame()
        self.PreCreate(pre)
        get_resources().LoadOnFrame(pre, parent, "MainFrame")
        self.PostCreate(pre)

        # Define variables for the controls, bind event handlers





# ------------------------ Resource data ----------------------

def __init_resources():
    global __res
    __res = xrc.EmptyXmlResource()

    __res.Load('twoBtns.xrc')

It's a little ugly, but if you can read normal wxPython, then you should be able to figure this out. Now let's create a subclass of this code. The main reason we want to do this is so that we can change the XRC file and the subsequent generated code and our subclass can basically just stay the same. It helps us to separate the model (the XML) from the view (the wxPython code). Anyway, here's the simple example:

# twoBtns_xrc_subclass.py

import twoBtns_xrc
import wx

########################################################################
class XrcFrameSubClass(twoBtns_xrc.xrcMainFrame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        twoBtns_xrc.xrcMainFrame.__init__(self, parent=None)
        self.Show()
        
if __name__ == "__main__":
    app = wx.App(False)
    frame = XrcFrameSubClass()
    app.MainLoop()

Notice that we import the module "twoBtns_xrc", which is similar to what I called the XRCfile. XRCed adds the "_xrc" part to the Python file name. Once we have that imported, we can access the XRC Frame object and subclass it. This example is kind of boring, so let's add some events. Re-open the XRC file in XRCed and select one of the buttons. The last tab on the left should be labeled Code. Choose that one and put a checkmark next to the EVT_BUTTON event. Do the same thing for the other button. Save the file and then regenerate the Python file. You should have something like this:

# This file was automatically generated by pywxrc.
# -*- coding: UTF-8 -*-

import wx
import wx.xrc as xrc

__res = None

def get_resources():
    """ This function provides access to the XML resources in this module."""
    global __res
    if __res == None:
        __init_resources()
    return __res




class xrcMainFrame(wx.Frame):
#!XRCED:begin-block:xrcMainFrame.PreCreate
    def PreCreate(self, pre):
        """ This function is called during the class's initialization.
        
        Override it for custom setup before the window is created usually to
        set additional window styles using SetWindowStyle() and SetExtraStyle().
        """
        pass
        
#!XRCED:end-block:xrcMainFrame.PreCreate

    def __init__(self, parent):
        # Two stage creation (see http://wiki.wxpython.org/index.cgi/TwoStageCreation)
        pre = wx.PreFrame()
        self.PreCreate(pre)
        get_resources().LoadOnFrame(pre, parent, "MainFrame")
        self.PostCreate(pre)

        # Define variables for the controls, bind event handlers

        self.Bind(wx.EVT_BUTTON, self.OnButton_okBtn, id=xrc.XRCID('okBtn'))
        self.Bind(wx.EVT_BUTTON, self.OnButton_cancelBtn, id=xrc.XRCID('cancelBtn'))

#!XRCED:begin-block:xrcMainFrame.OnButton_okBtn
    def OnButton_okBtn(self, evt):
        # Replace with event handler code
        print "OnButton_okBtn()"
#!XRCED:end-block:xrcMainFrame.OnButton_okBtn        

#!XRCED:begin-block:xrcMainFrame.OnButton_cancelBtn
    def OnButton_cancelBtn(self, evt):
        # Replace with event handler code
        print "OnButton_cancelBtn()"
#!XRCED:end-block:xrcMainFrame.OnButton_cancelBtn        




# ------------------------ Resource data ----------------------

def __init_resources():
    global __res
    __res = xrc.EmptyXmlResource()

    __res.Load('twoBtns_v2.xrc')

Now we're getting somewhere. Let's change out subclass code a little bit to adhere to the changes we made in our parent class.

# twoBtns_xrc_subclass_v2.py

from twoBtns_xrc_v2 import xrcMainFrame
import wx

########################################################################
class XrcFrameSubClass(xrcMainFrame):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        xrcMainFrame.__init__(self, parent=None)
        self.Show()
        
    #----------------------------------------------------------------------
    def OnButton_okBtn(self, event):
        """"""
        print "You pressed the OK button!"
        
    #----------------------------------------------------------------------
    def OnButton_cancelBtn(self, event):
        """"""
        print "You pressed the Cancel button!"
        
if __name__ == "__main__":
    app = wx.App(False)
    frame = XrcFrameSubClass()
    app.MainLoop()

Now we can override the button events. What this means is that all the button building and event binding is done for us automatically by XRCed. All we have to do is subclass the generated Python code and flesh out the event handlers. You may have noticed that there's a "Generate gettext strings" option as well. You can use this to generate a function that will return all the labels in your code. Why would you want to do that? Well, it makes it easy for you to translate the labels to other languages. See the wxPython wiki page on Internationalization for more information.

Wrapping Up

This covers the basics of using the XRCed application. Hopefully you know enough now to use it wisely and will be able to create some truly amazing code using these shortcuts. If you need help, be sure to check the links in the following section, email the wxPython mailing list or try bugging the wx guys on the IRC channel.

Further Reading

Copyright © 2024 Mouse Vs Python | Powered by Pythonlibrary