How to Use wxPython Demo Code Outside the Demo

Every now and then, someone will ask about how they can run the demo code from wxPython’s demo outside of the demo. In other words, they wonder how you can extract the code from the demo and run it in your own. I think I wrote about this very topic quite some time ago on the wxPython wiki, but I thought I should write on the topic here as well.


What to do about the log

The first issue that I always see is that the demo code is riddled with calls to some kind of log. It’s always writing to that log to help the developer see how different events get fired or how different methods get called. This is all well and good, but it makes just copying the code out of the demo difficult. Let’s take the code from the wx.ListBox demo as an example and see if we can make it work outside of the demo. Here is the demo code:

import wx

#----------------------------------------------------------------------
# BEGIN Demo Code
class FindPrefixListBox(wx.ListBox):
    def __init__(self, parent, id, pos=wx.DefaultPosition, size=wx.DefaultSize,
                 choices=[], style=0, validator=wx.DefaultValidator):
        wx.ListBox.__init__(self, parent, id, pos, size, choices, style, validator)
        self.typedText = ''
        self.log = parent.log
        self.Bind(wx.EVT_KEY_DOWN, self.OnKey)


    def FindPrefix(self, prefix):
        self.log.WriteText('Looking for prefix: %s\n' % prefix)

        if prefix:
            prefix = prefix.lower()
            length = len(prefix)

            # Changed in 2.5 because ListBox.Number() is no longer supported.
            # ListBox.GetCount() is now the appropriate way to go.
            for x in range(self.GetCount()):
                text = self.GetString(x)
                text = text.lower()

                if text[:length] == prefix:
                    self.log.WriteText('Prefix %s is found.\n' % prefix)
                    return x

        self.log.WriteText('Prefix %s is not found.\n' % prefix)
        return -1

    def OnKey(self, evt):
        key = evt.GetKeyCode()

        if key >= 32 and key <= 127:
            self.typedText = self.typedText + chr(key)
            item = self.FindPrefix(self.typedText)

            if item != -1:
                self.SetSelection(item)

        elif key == wx.WXK_BACK:   # backspace removes one character and backs up
            self.typedText = self.typedText[:-1]

            if not self.typedText:
                self.SetSelection(0)
            else:
                item = self.FindPrefix(self.typedText)

                if item != -1:
                    self.SetSelection(item)
        else:
            self.typedText = ''
            evt.Skip()

    def OnKeyDown(self, evt):
        pass


#---------------------------------------------------------------------------

class TestListBox(wx.Panel):
    def __init__(self, parent, log):
        self.log = log
        wx.Panel.__init__(self, parent, -1)

        sampleList = ['zero', 'one', 'two', 'three', 'four', 'five',
                      'six', 'seven', 'eight', 'nine', 'ten', 'eleven',
                      'twelve', 'thirteen', 'fourteen']

        wx.StaticText(self, -1, "This example uses the wx.ListBox control.", (45, 10))
        wx.StaticText(self, -1, "Select one:", (15, 50))
        self.lb1 = wx.ListBox(self, 60, (100, 50), (90, 120), sampleList, wx.LB_SINGLE)
        self.Bind(wx.EVT_LISTBOX, self.EvtListBox, self.lb1)
        self.Bind(wx.EVT_LISTBOX_DCLICK, self.EvtListBoxDClick, self.lb1)
        self.lb1.Bind(wx.EVT_RIGHT_UP, self.EvtRightButton)
        self.lb1.SetSelection(3)
        self.lb1.Append("with data", "This one has data");
        self.lb1.SetClientData(2, "This one has data");


        wx.StaticText(self, -1, "Select many:", (220, 50))
        self.lb2 = wx.ListBox(self, 70, (320, 50), (90, 120), sampleList, wx.LB_EXTENDED)
        self.Bind(wx.EVT_LISTBOX, self.EvtMultiListBox, self.lb2)
        self.lb2.Bind(wx.EVT_RIGHT_UP, self.EvtRightButton)
        self.lb2.SetSelection(0)

        sampleList = sampleList + ['test a', 'test aa', 'test aab',
                                   'test ab', 'test abc', 'test abcc',
                                   'test abcd' ]
        sampleList.sort()
        wx.StaticText(self, -1, "Find Prefix:", (15, 250))
        fp = FindPrefixListBox(self, -1, (100, 250), (90, 120), sampleList, wx.LB_SINGLE)
        fp.SetSelection(0)


    def EvtListBox(self, event):
        self.log.WriteText('EvtListBox: %s, %s, %s\n' %
                           (event.GetString(),
                            event.IsSelection(),
                            event.GetSelection()
                            # event.GetClientData()
                            ))

        lb = event.GetEventObject()
        # data = lb.GetClientData(lb.GetSelection())

        # if data is not None:
            # self.log.WriteText('\tdata: %s\n' % data)

    def EvtListBoxDClick(self, event):
        self.log.WriteText('EvtListBoxDClick: %s\n' % self.lb1.GetSelection())
        self.lb1.Delete(self.lb1.GetSelection())

    def EvtMultiListBox(self, event):
        self.log.WriteText('EvtMultiListBox: %s\n' % str(self.lb2.GetSelections()))

    def EvtRightButton(self, event):
        self.log.WriteText('EvtRightButton: %s\n' % event.GetPosition())

        if event.GetEventObject().GetId() == 70:
            selections = list(self.lb2.GetSelections())
            selections.reverse()

            for index in selections:
                self.lb2.Delete(index)
#----------------------------------------------------------------------
# END Demo Code
#----------------------------------------------------------------------

I'm not going to explain the demo code itself. Instead I will focus on the issue that this code presents when wanting to try running it outside of the demo. There is a runTest function at the end of the demo that I didn't copy because that code won't do anything if you copy it outside of the demo. You see, the demo code has some kind of wrapper around it that makes it work. You will need to add your own "wrapper" of sorts if you want to use demo code.

The main issue that this code presents is that a lot of the methods have a call to self.log.WriteText. You can't really tell from the code what the log object is, but you do know that it has a WriteText method. In the demo, you will notice that when one of those methods fire, the WriteText calls seem to write to the text control at the bottom of the demo. So the log must be a text control widget!

There are a lot of different approaches to solving the log issue. Here are my top three:

  • Remove all the calls to self.log.WriteText
  • Create my own text control and pass it in
  • Create a simple class with a WriteText method

I have done the first choice on many occasions as it's a simple way to get going. But for a tutorial it's kind of boring, so instead we will choose the third option and create a class with a WriteText method! Add the following code to the same file that contains the code above:


#----------------------------------------------------------------------
# Start Your own code here           
class FakeLog:
    """
    The log in the demo is a text control, so just create a class
    with an overridden WriteText function
    """
    
    def WriteText(self, string):
        print(string)
   
# Create a frame that can wrap your demo code (works in most cases)

class MyFrame(wx.Frame):
    
    def __init__(self):
        wx.Frame.__init__(self, None, title='Listbox demo', 
                          size=(800,600))
        log = FakeLog()
        panel = TestListBox(self, log=log)
        
        self.Show()
        
if __name__ == '__main__':
    app = wx.App(False)
    frame = MyFrame()
    app.MainLoop()

Here we just create a FakeLog with a WriteText method that accepts a string as its sole parameter. All that method does is print the string to stdout. Then we create a subclass of wx.Frame, initialize our fake log and the demo code and show our frame. Now we have a working piece of demo code that's not in the demo! You can get the full code over on Github if you'd like to.


Other Demo Issues

There are some other demos that don't follow the exact same API that the ListBox demo does. For example, if you try using the class I created above for the wx.Button demo, you will find that its log object calls a write() method instead of a WriteText() method. The solution is obvious in this case in that we just need to add a second method to our fake logging class:

class FakeLog:
    """
    The log in the demo is a text control, so just create a class
    with an overridden WriteText function
    """
    
    def WriteText(self, string):
        print(string)
    
    def write(self, string):
        print(string)

Now our demo running code is a bit more flexible. However when I had one of my readers testing this code out, they noticed an issue with the wx.ListCtrl demo. The issue is that it imports a module called "images". There are actually several demos that reference this module. You will just need to copy images.py from the demo and put it in the same location as the script that you are writing so you can import it.

Note: I have had one report that the images.py file included with the latest beta of wxPython 4 didn't work for them and they had to grab a copy from an older version of the demo. I haven't had this issue myself, but keep that in mind.


Wrapping Up

Now you should have the tools you need to make most of the demos from the wxPython demo work in your own code. Go grab some code and give it a try! Happy coding!