wxPython: Using ObjectListView instead of a ListCtrl

The wxPython ListCtrl is a very handy widget. Unfortunately, it can be a pain to use as well. This discovery caused Phillip Piper, missionary to Mozambique, to write ObjectListView, a wrapper for the wx.ListCtrl. ObjectListView actually adds functionality because it uses objects to create its rows and thus, it makes gettings information from multiple columns much easier. Mr. Piper also added lots of other conveniences that makes adding custom editors easier, alternating the color of rows, automatically sorts rows, and much, much more! This article will help you learn some of the basics of using ObjectListView so that you’ll be able to use it in your future projects. This is not meant to be an exhaustive look at the control as it is actually very well documented.

It should be noted that ObjectListView is not a drop-in replacement for a standard list control. The setup is quite a bit different. Also, this is a third party library that is not included with wxPython, so you will need to download it. Now that all the disclaimers are out of the way, let’s take a look at a simple example:

import wx
from ObjectListView import ObjectListView, ColumnDefn

########################################################################
class Book(object):
    """
    Model of the Book object

    Contains the following attributes:
    'ISBN', 'Author', 'Manufacturer', 'Title'
    """
    #----------------------------------------------------------------------
    def __init__(self, title, author, isbn, mfg):
        self.isbn = isbn
        self.author = author
        self.mfg = mfg
        self.title = title
       
    
########################################################################
class MainPanel(wx.Panel):
    #----------------------------------------------------------------------
    def __init__(self, parent):
        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
        self.products = [Book("wxPython in Action", "Robin Dunn",
                              "1932394621", "Manning"),
                         Book("Hello World", "Warren and Carter Sande",
                              "1933988495", "Manning")
                         ]

        self.dataOlv = ObjectListView(self, wx.ID_ANY, style=wx.LC_REPORT|wx.SUNKEN_BORDER)
        self.setBooks()

        # Allow the cell values to be edited when double-clicked
        self.dataOlv.cellEditMode = ObjectListView.CELLEDIT_SINGLECLICK
        
        # create an update button
        updateBtn = wx.Button(self, wx.ID_ANY, "Update OLV")
        updateBtn.Bind(wx.EVT_BUTTON, self.updateControl)

        # Create some sizers
        mainSizer = wx.BoxSizer(wx.VERTICAL)        

        mainSizer.Add(self.dataOlv, 1, wx.ALL|wx.EXPAND, 5)
        mainSizer.Add(updateBtn, 0, wx.ALL|wx.CENTER, 5)
        self.SetSizer(mainSizer)
        
    #----------------------------------------------------------------------
    def updateControl(self, event):
        """
        
        """
        print "updating..."
        product_dict = [{"title":"Core Python Programming", "author":"Wesley Chun",
                         "isbn":"0132269937", "mfg":"Prentice Hall"},
                        {"title":"Python Programming for the Absolute Beginner",
                         "author":"Michael Dawson", "isbn":"1598631128",
                         "mfg":"Course Technology"},
                        {"title":"Learning Python", "author":"Mark Lutz",
                         "isbn":"0596513984", "mfg":"O'Reilly"}
                        ]
        data = self.products + product_dict
        self.dataOlv.SetObjects(data)
        
    #----------------------------------------------------------------------
    def setBooks(self, data=None):
        self.dataOlv.SetColumns([
            ColumnDefn("Title", "left", 220, "title"),
            ColumnDefn("Author", "left", 200, "author"),
            ColumnDefn("ISBN", "right", 100, "isbn"),            
            ColumnDefn("Mfg", "left", 180, "mfg")
        ])
        
        self.dataOlv.SetObjects(self.products)

########################################################################
class MainFrame(wx.Frame):
    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, parent=None, id=wx.ID_ANY, 
                          title="ObjectListView Demo", size=(800,600))
        panel = MainPanel(self)
        
########################################################################
class GenApp(wx.App):
    
    #----------------------------------------------------------------------
    def __init__(self, redirect=False, filename=None):
        wx.App.__init__(self, redirect, filename)
        
    #----------------------------------------------------------------------
    def OnInit(self):
        # create frame here
        frame = MainFrame()
        frame.Show()
        return True
    
#----------------------------------------------------------------------
def main():
    """
    Run the demo
    """
    app = GenApp()
    app.MainLoop()

if __name__ == "__main__":
    main()

If you run this code, you should end up seeing something like this:

Now let’s take a look at what all this does. First off, I create a generic “Book” class with some properties: isbn, author, mfg and title. We’ll use this class for creating rows in the ObjectListView. Next we create a standard panel and put an ObjectListView and a button widget on it. You’ll also notice that there’s a short list of “Book” objects. The ObjectListView is set to report mode with the LC_REPORT style flag. It has other modes too, but I won’t be covering those. The report mode looks most like the details mode in Windows Explorer.

The next piece is a little weird:

self.dataOlv.cellEditMode = ObjectListView.CELLEDIT_SINGLECLICK

This code tells our widget to allow editing of all the cells in the row (except the first) by double-clicking them. I don’t know why it was designed this way as it looks like all you should have to do is single click one. Even the documentation says that a single click should be enough. Maybe it’s a Windows limitation. Anyway, to edit the first cell of any row, just select it and hit F2.

The last few lines in the initialization method just put the widgets into sizers. The next piece of interesting code is in the updateControl method, where we actually update our ObjectListView’s contents. I show two different ways to do the update here. The first is to just use the product list of Book objects and call the ObjectListView’s SetObjects method with the list passed in. The second way is to use a dictionary. The dictionary’s keys must match the ColumnDefn’s valueGetter name (which we’ll see in the setBooks method). The dictionary values can be whatever you want. In my example, I actually combine the list of Book objects and the list of dictionaries and call SetObjects on the result.

In the setBooks method, we define the ObjectListView’s columns. This is done by passing a list of ColumnDefn objects to the ObjectListView’s SetColumns method. The ColumnDefn has many parameters, but we’re only going to cover the first four. Argument one is the title for the column; argument two is the alignment for the column as a whole; argument three is the width of the column; and argument four is the valueGetter name. This name must match either the keys in the dictionary method mentioned above or the properties of the class that you use (in this case, my Book class). Otherwise, some data will not appear in the widget.

If you want to learn about accessing some of the row object’s data, then add another button to this application and bind it to the following function:

def getRowInfo(self, event):
    """"""
    rowObj = self.dataOlv.GetSelectedObject()
    print rowObj.author
    print rowObj.title

Now you can select a row and use the ObjectListView’s GetSelectedObject method to get the row object. Once you have that, you can access the object’s properties, like the author and title and whatever else you have defined. This is much easier than the ListCtrl where you have to get the column and row to find the information for each item.

That covers the basics of using an ObjectListCtrl. Be sure to download the source as it has a bunch of interesting demos including one that allows the user to edit some cells with an owner drawn combobox! I’ll be writing another article soon that uses ObjectListView. It will show you how to save and restore data and create a fun little application too!

This code was tested using Windows Vista, wxPython 2.8.10.1 (unicode) and Python 2.5.

Further Reading

Downloads

4 thoughts on “wxPython: Using ObjectListView instead of a ListCtrl”

  1. Thanks Mike,
    I Just wanted to use ListCtrl and I remembered the pain I got last time I used it. Bang! I remembered the gem and now I visited your blog and saw the post
    God bless you for good work.

    If you cover something on Printing with this widget it will be great
    Steve

  2. Thanks Mike,
    I Just wanted to use ListCtrl and I remembered the pain I got last time I used it. Bang! I remembered the gem and now I visited your blog and saw the post
    God bless you for good work.

    If you cover something on Printing with this widget it will be great
    Steve

  3. Very good article. A good read. Thanks. I enjoyed reading this.Thanks for sharing your thoughts. Thank you. A very good piece.Great post! I want to know when you update your blog, where can i subscribe to your blog.

  4. Very good article. A good read. Thanks. I enjoyed reading this.Thanks for sharing your thoughts. Thank you. A very good piece.Great post! I want to know when you update your blog, where can i subscribe to your blog.

Comments are closed.