wxPython: Storing Objects in ComboBox or ListBox Widgets

Earlier this week, there was a discussion on the wxPython IRC channel about how to store objects in wx.ListBox. Then later on that day, there was a question on StackOverflow about the same thing, but in relation to the wx.ComboBox. Fortunately, both of these widgets inherit from wx.ItemContainer and contain the Append method, which allows you to associate an object with an item in these widgets. In this article, you will find out how this is done.

Adding Objects to wx.ListBox

We'll start with the ListBox first. Let's just jump into the code as I think you'll learn it faster that way.

import wx

class Car:
    """"""

    def __init__(self, id, model, make, year):
        """Constructor"""
        self.id = id
        self.model = model
        self.make = make
        self.year = year
    
    
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        
        cars = [Car(0, "Ford", "F-150", "2008"),
                Car(1, "Chevrolet", "Camaro", "2010"),
                Car(2, "Nissan", "370Z", "2005")]
        
        sampleList = []
        self.cb = wx.ComboBox(panel,
                              size=wx.DefaultSize,
                              choices=sampleList)
        self.widgetMaker(self.cb, cars)
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.cb, 0, wx.ALL, 5)
        panel.SetSizer(sizer)
        
    def widgetMaker(self, widget, objects):
        """"""
        for obj in objects:
            widget.Append(obj.make, obj)
        widget.Bind(wx.EVT_COMBOBOX, self.onSelect)
                        
    def onSelect(self, event):
        """"""
        print("You selected: " + self.cb.GetStringSelection())
        obj = self.cb.GetClientData(self.cb.GetSelection())
        text = """
        The object's attributes are:
        %s  %s    %s  %s
        
        """ % (obj.id, obj.make, obj.model, obj.year)
        print(text)
 
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()

Now, how does this work? Let's take some time and unpack this example. First off, we create a super-simple Car class where we define four attributes: an id, model, make and year. Then we create a simple frame with a panel and the ListBox widget. As you can see, we use the ListBox's inherited Append method to add each Car object's "make" string and then the object itself. This allows us to associate each item in the list box to an object. Finally, we bind the ListBox to EVT_LISTBOX so we can find out how to access that object when we select an item from the widget.

To see how this is accomplished, check out the onSelect method. Here we can see that we need to call the ListBox's GetClientData method and pass it the current selection. This will return the object that we associated earlier. Now we can access each of the method's attributes. In this example, we just print all that out to stdout. Now let's look at how this is done with the wx.ComboBox.

Adding Objects to the wx.ComboBox

The code for the wx.ComboBox is practically the same, so for fun we'll do a little refactoring. Take a look:

import wx

class Car:
    """"""

    def __init__(self, id, model, make, year):
        """Constructor"""
        self.id = id
        self.model = model
        self.make = make
        self.year = year       
    
    
class MyForm(wx.Frame):
 
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
 
        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        
        cars = [Car(0, "Ford", "F-150", "2008"),
                Car(1, "Chevrolet", "Camaro", "2010"),
                Car(2, "Nissan", "370Z", "2005")]
        
        sampleList = []
        self.cb = wx.ComboBox(panel,
                              size=wx.DefaultSize,
                              choices=sampleList)
        self.widgetMaker(self.cb, cars)
        
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.cb, 0, wx.ALL, 5)
        panel.SetSizer(sizer)
        
    def widgetMaker(self, widget, objects):
        """"""
        for obj in objects:
            widget.Append(obj.make, obj)
        widget.Bind(wx.EVT_COMBOBOX, self.onSelect)
                        
    def onSelect(self, event):
        """"""
        print("You selected: " + self.cb.GetStringSelection())
        obj = self.cb.GetClientData(self.cb.GetSelection())
        text = """
        The object's attributes are:
        %s  %s    %s  %s
        
        """ % (obj.id, obj.make, obj.model, obj.year)
        print(text)
 
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm()
    frame.Show()
    app.MainLoop()

In this example, the steps are exactly the same. But what is we had multiple ComboBoxes that we had to do this sort of thing for? That would be a lot of redundant code. Thus, we write up a simple helper method called widgetMaker that will do the appending and event binding for us. We could make it build the widget, add it to a sizer and other things too, but we'll keep it simple for this example. Anyway, to make it work, we pass in the ComboBox widget along with a list of objects that we want to add to the widget. The widgetMaker will append those objects to the ComboBox for us. The rest of the code is the same, except for the slightly different event that we needed to bind to.

Wrapping Up

As you can see, this is a pretty straight-forward little exercise, but it makes your GUI's more robust. You might do this for database applications. I can see myself using this with SqlAlchemy result sets. Be creative and I'm sure you'll find good uses for it as well.

Additional Reading

Copyright © 2022 Mouse Vs Python | Powered by Pythonlibrary