wxPython: Adding Checkboxes to ObjectListView

This week I spent some time learning how to add check boxes to the ObjectListView widget in wxPython. If you don't know, ObjectListView is a 3rd party wrapper for the wx.ListCtrl widget that makes using the ListCtrl much easier. You can read all about it in this older article from the archives. I had a requirement where I needed to have a check box next to each item in the report view of the widget. After some digging on the ObjectListView website, I found an article that sort of explained how to do it. According to the documentation, I could use the CreateCheckStateColumn method or register a column and use InstallCheckStateColumn. In this article, we'll be focusing on the CreateCheckStateColumn method.

Getting Started

First off, you'll need to install the following if you haven't done so already:

How to Create and Toggle Checkboxes

olvcheckbox

Now that you've got that done, we can begin coding. I always found a working example to be the best way to learn, so here's the first piece of the puzzle:

# OLVcheckboxes.py

import wx
from ObjectListView import ObjectListView, ColumnDefn


class Results(object):
    """"""

    def __init__(self, tin, zip_code, plus4, name, address):
        """Constructor"""
        self.tin = tin
        self.zip_code = zip_code
        self.plus4 = plus4
        self.name = name
        self.address = address
    

class ProvPanel(wx.Panel):
    """"""

    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent=parent)
        
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        
        self.test_data = [Results("123456789", "50158", "0065", "Patti Jones",
                                  "111 Centennial Drive"),
                          Results("978561236", "90056", "7890", "Brian Wilson",
                                  "555 Torque Maui"),
                          Results("456897852", "70014", "6545", "Mike Love", 
                                  "304 Cali Bvld")
                          ]
        self.resultsOlv = ObjectListView(self, 
            style=wx.LC_REPORT|wx.SUNKEN_BORDER)
        
        self.setResults()
        
        toggleBtn = wx.Button(self, label="Toggle Checks")
        toggleBtn.Bind(wx.EVT_BUTTON, self.onToggle)
        
        mainSizer.Add(self.resultsOlv, 1, wx.EXPAND|wx.ALL, 5)
        mainSizer.Add(toggleBtn, 0, wx.CENTER|wx.ALL, 5)
        self.SetSizer(mainSizer)
    
    def onToggle(self, event):
        """
        Toggle the check boxes
        """
        objects = self.resultsOlv.GetObjects()
        for obj in objects:
            print self.resultsOlv.IsChecked(obj)
            self.resultsOlv.ToggleCheck(obj)
        self.resultsOlv.RefreshObjects(objects)
    
    def setResults(self):
        """"""
        self.resultsOlv.SetColumns([
            ColumnDefn("TIN", "left", 100, "tin"),
            ColumnDefn("Zip", "left", 75, "zip_code"),
            ColumnDefn("+4", "left", 50, "plus4"),
            ColumnDefn("Name", "left", 150, "name"),
            ColumnDefn("Address", "left", 200, "address")
            ])
        self.resultsOlv.CreateCheckStateColumn()
        self.resultsOlv.SetObjects(self.test_data)
        

class ProvFrame(wx.Frame):
    """"""

    def __init__(self):
        """Constructor"""
        title = "OLV Checkbox Tutorial"
        wx.Frame.__init__(self, parent=None, title=title, 
                          size=(1024, 768))
        panel = ProvPanel(self)
        

if __name__ == "__main__":
    app = wx.App(False)
    frame = ProvFrame()
    frame.Show()
    app.MainLoop()

Let's break this down a little. As you can see, we put the wx.Frame and the wx.Panel into separate classes. This makes it easier to modularize in the future if need be. For example, if the panel code got to be too many lines, we could just copy it into a new module and import it into the main code. Anyway, here we create the various widgets we care about in the panel class's __init__ method. To get check boxes, we need to add the CreateCheckStateColumn call right before the SetObjects call which is in the setResults method.

The other important method we care about is the onToggle method where we learn how to toggle the check boxes using the ToggleCheck method. We start out by grabbing all the item object in the widget via GetObjects and then we loop over them. In this example, we print out whether they're checked or not by using the handy IsChecked method. This is useful for debugging or if you want to toggle items individually based on their state. Lastly, you need to call RefreshObjects to refresh the ObjectListCtrl so you can see the check box's new state. Otherwise, the display doesn't update and you cannot tell if the items are checked or not. You can also toggle the check boxes by selecting the item(s) and pressing the spacebar.

Toggling the Checkboxes with SetCheckState

olvcheckbox2

There is another way to change the check box's state and that is to use the SetCheckState method. Here's some code demonstrating how it is accomplished:

# OLVcheckboxes2.py

import wx
from ObjectListView import ObjectListView, ColumnDefn


class Results(object):
    """"""

    def __init__(self, tin, zip_code, plus4, name, address):
        """Constructor"""
        self.tin = tin
        self.zip_code = zip_code
        self.plus4 = plus4
        self.name = name
        self.address = address
    

class OLVCheckPanel(wx.Panel):
    """"""

    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent=parent)
        
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
        
        self.test_data = [Results("123456789", "50158", "0065", "Patti Jones",
                                  "111 Centennial Drive"),
                          Results("978561236", "90056", "7890", "Brian Wilson",
                                  "555 Torque Maui"),
                          Results("456897852", "70014", "6545", "Mike Love", 
                                  "304 Cali Bvld")
                          ]
        self.resultsOlv = ObjectListView(self, 
            style=wx.LC_REPORT|wx.SUNKEN_BORDER)
        
        self.setResults()
        
        checkBtn = wx.Button(self, label="Check")
        checkBtn.Bind(wx.EVT_BUTTON, self.onCheck)
        btnSizer.Add(checkBtn, 0, wx.ALL, 5)
        
        uncheckBtn = wx.Button(self, label="Uncheck")
        uncheckBtn.Bind(wx.EVT_BUTTON, self.onUncheck)
        btnSizer.Add(uncheckBtn, 0, wx.ALL, 5)
        
        mainSizer.Add(self.resultsOlv, 1, wx.EXPAND|wx.ALL, 5)
        mainSizer.Add(btnSizer, 0, wx.CENTER|wx.ALL, 5)
        self.SetSizer(mainSizer)
    
    def onCheck(self, event):
        """"""
        objects = self.resultsOlv.GetObjects()
        
        for obj in objects:
            self.resultsOlv.SetCheckState(obj, True)
        self.resultsOlv.RefreshObjects(objects)
        
    def onUncheck(self, event):
        """"""
        objects = self.resultsOlv.GetObjects()
                
        for obj in objects:
            self.resultsOlv.SetCheckState(obj, False)
        self.resultsOlv.RefreshObjects(objects)        
        
    def setResults(self):
        """"""
        self.resultsOlv.SetColumns([
            ColumnDefn("TIN", "left", 100, "tin"),
            ColumnDefn("Zip", "left", 75, "zip_code"),
            ColumnDefn("+4", "left", 50, "plus4"),
            ColumnDefn("Name", "left", 150, "name"),
            ColumnDefn("Address", "left", 200, "address")
            ])
        self.resultsOlv.CreateCheckStateColumn()
        self.resultsOlv.SetObjects(self.test_data)
        
    
class OLVCheckFrame(wx.Frame):
    """"""

    def __init__(self):
        """Constructor"""
        title = "OLV Checkbox Tutorial"
        wx.Frame.__init__(self, parent=None, title=title, size=(1024, 768))
        panel = OLVCheckPanel(self)
        
    
if __name__ == "__main__":
    app = wx.App(False)
    frame = OLVCheckFrame()
    frame.Show()
    app.MainLoop()

As you can see, we have two buttons in this example. A Check and an Uncheck button. The code is basically the same, but this time when we loop over the objects we call SetCheckState and pass it the item object and a bool of either True or False. Then we refresh the screen as before.

Adding Ability to Select/Deselect All

Update (12/12/2013): I actually noticed an annoying bug in the toggling of the checkboxes this week when I decided to re-use the implementation shown above. I wanted to toggle the widgets so that all were either selected or unselected. The code above will just toggle them no matter what state they are already in. What that means is that if you have checked 2 out of 5 items and you hit the toggle button, the checked items become unchecked and the unchecked ones become checked. To mitigate this, I ended up needing to change the event handler slightly to the following:

def onToggle(self, event):
    """
    Toggle the check boxes
    """
    objects = self.resultsOlv.GetObjects()
    
    for obj in objects:
        if self.toggleBtn.GetValue():
            if not self.resultsOlv.IsChecked(obj):
                self.resultsOlv.ToggleCheck(obj)
        else:
            if self.resultsOlv.IsChecked(obj):
                self.resultsOlv.ToggleCheck(obj)
    
    self.resultsOlv.RefreshObjects(objects)

This code checks to see if the toggle button is checked via GetValue. If it is, then we do an additional check to see if the line item is not checked. If it's not, then we check it. The other part of the conditional does the opposite. Also note that you will need to change the toggleBtn variable into a class property (self.toggleBtn) to make this code work.

Update (07/28/2015): I was recently contacted by one of my astute readers (Amit Vinchhi) who mentioned that I could simplify the toggle method I mention above to the following piece of code:

def onToggle(self, event):
    """
    Toggle the check boxes
    """
    objects = self.resultsOlv.GetObjects()
    check = self.toggleBtn.GetValue()

    for obj in objects:
        self.resultsOlv.SetCheckState(obj, check)
    self.resultsOlv.RefreshObjects(objects)

Of course, this assumes that you change the original wx.Button to a wx.ToggleButton and change the button's binding to use wx.EVT_TOGGLEBUTTON. Basically you end up with this:

self.toggleBtn = wx.ToggleButton(self, label="Toggle Checks")
self.toggleBtn.Bind(wx.EVT_TOGGLEBUTTON, self.onToggle)

Then it works quite well. Thanks Amit!

Wrapping Up

Now you know how to add check boxes to your wxPython project if you're using ObjectListView. If you need to use check boxes in the normal wx.ListCtrl, you can use a mixin that's included with wx. Here's how you access it:

from wx.lib.mixins.listctrl import CheckListCtrlMixin

There is an example in the wxPython demo package, which you can download from the wxPython website. I suspect that you could also use check boxes in the DVCListCtrl with that mixin as well or you could get very customized with the UltimateListCtrl. I prefer using the ObjectListView control as of right now though. I tested this code using Python 2.6.6 and Python 2.7.3 with wxPython 2.8.12 and wxPython 2.9.4 classic. I also used ObjectListview 1.2.

Further Reading

Downloads

Copyright © 2024 Mouse Vs Python | Powered by Pythonlibrary