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
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:
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:
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.
Excelent! wxPython