wxPython: An Introduction to XRC

Posted by Mike on May 11th, 2010 filed in Cross-Platform, Python, wxPython

Have you ever wondered if you could create a wxPython program using XML? Well, I never did either, but there is a way and its name is XRC. In fact, wxPython comes with an editor called XRCed that you can use to layout your GUI and generate the XML code with. In this article, we’ll give you a quick walk-through of XRC and how to use it to create a couple GUI skeletons. We will look at two examples that use only XRC controls and then a third that mixes in some additional non-XRC widgets.

Creating a Login Screen with XRC

A common dialog that we often see is a login dialog. I used XRCed to create the following XML code:

<?xml version="1.0" encoding="cp1252"?>
<resource>
  <object class="wxFrame" name="mainFrame">
    <object class="wxPanel" name="panel">
      <object class="wxBoxSizer">
        <orient>wxVERTICAL</orient>
        <object class="sizeritem">
          <object class="wxStaticText" name="handle">
            <label/>
          </object>
        </object>
        <object class="sizeritem">
          <object class="wxFlexGridSizer">
            <object class="sizeritem">
              <object class="wxStaticText" name="userLbl">
                <label>Username:</label>
              </object>
              <flag>wxALL</flag>
              <border>5</border>
            </object>
            <object class="sizeritem">
              <object class="wxTextCtrl" name="userTxt"/>
            </object>
            <object class="sizeritem">
              <object class="wxStaticText" name="passwordLbl">
                <label>Password:</label>
              </object>
              <flag>wxALL</flag>
              <border>5</border>
            </object>
            <object class="sizeritem">
              <object class="wxTextCtrl" name="passwordTxt">
                <style>wxTE_PROCESS_ENTER|wxTE_PASSWORD</style>
              </object>
            </object>
            <object class="sizeritem">
              <object class="wxButton" name="loginBtn">
                <label>Login</label>
              </object>
              <flag>wxALL|wxALIGN_CENTRE</flag>
              <border>5</border>
            </object>
            <object class="sizeritem">
              <object class="wxButton" name="cancelBtn">
                <label>Cancel</label>
              </object>
              <flag>wxALL|wxALIGN_CENTRE</flag>
              <border>5</border>
            </object>
            <cols>2</cols>
            <rows>3</rows>
            <vgap>4</vgap>
            <hgap>2</hgap>
          </object>
          <border>5</border>
        </object>
      </object>
      <style/>
    </object>
    <size>200,100</size>
    <title>Login</title>
    <centered>1</centered>
  </object>
</resource>

To use XRC code in your wxPython, all you need to do is “import wx.xrc” or use “from wx import xrc”. Let’s see what the Python code looks like:

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

In the code above, we use xrc’s XmlResource method to open our XML file and load it in our program. Next, we use the resulting variable to load specific widgets from the file. In this case, we load just the frame by calling LoadFrame. Note that we passed None into the LoadFrame call. That first argument is the parent argument and since this frame shouldn’t have a parent, we passed it None. Lastly, we call the frame’s Show method so we can actually see our program. That’s all there is to it! Now let’s move onto something a little bit more complex.

Creating a Notebook with XRC

Creating a Notebook widget is a little trickier than just creating a frame. For one thing, when using a Notebook, you usually stick multiple panels on it. This can get confusing if your panels are complex. Thus, we’ll look at how to create a simple notebook and a slightly more complex version. Let’s start with the simple one first. If you want to follow along, open XRCed and see if you can copy the layout in the screenshot below:

XRCed layout of a Notebook

XRCed layout of a Notebook

The trick to adding pages to your notebook in XRCed is that you need to select the child panel and choose the NotebookPage tab that appears on the right. In there you can set the labels for the tabs. Let’s take a look at the generated XML:

<?xml version="1.0" ?>
<resource>
  <object class="wxFrame" name="DemoFrame">
    <object class="wxPanel" name="DemoPanel">
      <object class="wxBoxSizer">
        <orient>wxVERTICAL</orient>
        <object class="sizeritem">
          <object class="wxNotebook" name="DemoNotebook">
            <object class="notebookpage">
              <object class="wxPanel" name="tabOne"/>
              <label>tabOne</label>
            </object>
            <object class="notebookpage">
              <object class="wxPanel" name="tabTwo"/>
              <label>tabTwo</label>
            </object>
          </object>
          <option>1</option>
          <flag>wxALL|wxEXPAND</flag>
          <border>5</border>
        </object>
      </object>
    </object>
    <title>XRC Notebook Demo</title>
  </object>
</resource>

It’s pretty much the same as the code we saw previously. Note that we can embed sizer flags in the XML itself (e.g. wxALL|wx.EXPAND). That’s pretty neat! The code to load this notebook is almost exactly the same as the code we used for the login dialog earlier:

# notebookXrcDemo.py
import wx
from wx import xrc
 
class MyApp(wx.App):
    def OnInit(self):
        self.res = xrc.XmlResource("notebook2.xrc")
 
        self.frame = self.res.LoadFrame(None, 'DemoFrame')
 
        self.frame.Show()
        return True
 
if __name__ == "__main__":
    app = MyApp(False)
    app.MainLoop()

The only differences here are the names of the frame and the XRC file. Now let’s move on to our slightly more complex Notebook example. In this example, we will create a notebook XRC file and two Panel XRC files that we can use as tabs for the notebook. Our new Notebook’s XML is pretty much like the old one, so we’ll skip that. Instead, we’ll look at our Python code instead:

Notebook Demo

# notebookXrcDemo2.py
import wx
from wx import xrc
 
class MyApp(wx.App):
    def OnInit(self):
        res = xrc.XmlResource("notebook.xrc")
        frame = res.LoadFrame(None, "DemoFrame")
        panel = xrc.XRCCTRL(frame, "DemoPanel")
        notebook = xrc.XRCCTRL(panel, "DemoNotebook")
 
        # load another xrc file
        res = xrc.XmlResource("panelOne.xrc")
        tabOne = res.LoadPanel(notebook, "panelOne")
        notebook.AddPage(tabOne, "TabOne")
 
        # load the last xrc file
        res = xrc.XmlResource("panelTwo.xrc")
        tabTwo = res.LoadPanel(notebook, "panelTwo")
        notebook.AddPage(tabTwo, "tabTwo")
 
        frame.Show()
        return True
 
if __name__ == "__main__":
    app = MyApp(False)
    app.MainLoop()

Here we just extract the frame, panel and notebook objects from the first XRC file and use those as our basis for adding other controls. Loading the other two panels is a cinch as we just do what we did to load the original panel. Then we add our new panels to the notebook using the familiar AddPage methodology. Once that’s done, we show the frame and we’re done! The second panel has an empty ListCtrl in it and when I first created it, I kept getting error messages because I forgot to set its style. Make sure you tell it that you want it to be in List, Report or one of its other modes or you’ll have issues too.

Adding Controls Outside of XRC

One of the issues with XRC is that it only supports a small subset of the widgets available. Fortunately, there are ways to “teach” XRC how to use new controls, but that is beyond the scope of this introductory article. Instead, I’ll show you how to add the controls outside of XRC. The concept is the same as using normal widgets, so it’s really easy to understand. In fact, we’re going to take the second notebook example and add a PlateButton to it:

# notebookXrcDemo3.py
 
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)
        sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5)
        sizer.Add(btn)
        panel.SetSizer(sizer)
 
        frame.Show()
        return True
 
if __name__ == "__main__":
    app = MyApp(False)
    app.MainLoop()

Notice that all we had to do was take the XRC Panel widget and make it the PlateButton’s parent. Then we added the notebook and the button to a vertically oriented sizer. Now we know how to combine normal widgets with XRC widgets in our applications. I hope you’ve learned a lot from this article and will find it helpful in your own work.

Further Reading

Downloads

Print Friendly

  • MrMe

    Thanks Mike.
    Is there a noticeable performance hit having the controls rendered from xml at runtime?

  • driscollis

    Not that I've noticed. Admittedly, I haven't used a complex XRC file either.

    – Mike

  • driscollis

    Hi Joaquin,

    Good question! Two that I can think of off the top of my head are as follows:

    1) Users with XML experience can create or edit GUIs without learning wxPython
    2) Putting the layout stuff in XRC can help you conform to the MVC model better (the idea of separating your model and your controller)

    I'm sure there are other good reasons, but I don't know what they are.

    – Mike

  • MrMe

    Mike- if you have time- would you add a tutorial which contains several levels of nested sizers, using XRC?

    I usually use Box Sizers mostly, and try to avoid all the other types. They just seem complicated and not documented clearly. But whatever you decide to serve the greater good.