Search code examples
macoswindows-xpwxpython

Item Checking not possible with UltimateListCtrl in ULC_VIRTUAL mode


Following is the system and software info

Platforms: Windows XP and OSX Lion
Activestate Python 2.7.2
wxPython2.9-osx-cocoa-py2.7 (for OSX)
wxPython2.9-win32-py27 (for Windows XP)

I am trying to create a UltimateListCtrl using ULC_VIRTUAL and ULC_REPORT mode. I would like to know how can I put a checkbox beside the first column of every row and catch the event when a user checks the box. I was able to do the same using UltimateListCtrl without VIRTUAL mode. But, with the ULC_VIRTUAL flag ON, I don't know how to proceed. Following is the code I created, but this still doesn't allow me to check the boxes associated with the first column. Please help.

import wx
    import images
    import random
    import os, sys
from wx.lib.agw import ultimatelistctrl as ULC

class TestUltimateListCtrl(ULC.UltimateListCtrl):
    def __init__(self, parent, log):

        ULC.UltimateListCtrl.__init__(self, parent, -1, agwStyle=ULC.ULC_VIRTUAL|ULC.ULC_REPORT|ULC.ULC_SINGLE_SEL|ULC.ULC_VRULES|ULC.ULC_HRULES)
    self.SetItemCount(1000)
    self.table_fields=['First','Second','Third']
    field_index=0
        for field in self.table_fields:
        info = ULC.UltimateListItem()
        info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT | ULC.ULC_MASK_CHECK
        info._image = []
        info._format = wx.LIST_FORMAT_CENTER
        info._kind = 1      
        info._text = field
        info._font= wx.Font(13, wx.ROMAN, wx.NORMAL, wx.BOLD)
        self.InsertColumnInfo(field_index, info)
        self.SetColumnWidth(field_index,175)
        field_index += 1

    def getColumnText(self, index, col):
        item = self.GetItem(index, col)
        return item.GetText()

    def OnGetItemText(self, item, col):
        return "Item %d, Column %d" % (item,col)

    def OnGetItemColumnImage(self, item, col):
        return []

    def OnGetItemImage(self, item):
        return []

    def OnGetItemAttr(self, item):
        return None

    def OnGetItemTextColour(self, item, col):
        return None

    #def OnGetItemColumnCheck(self, item, col):
    #return True

    #def OnGetItemCheck(self, item):
    #return True

    def OnGetItemToolTip(self, item, col):
        return None

    def OnGetItemKind(self, item):
        return 1

    def OnGetItemColumnKind(self, item, col):
        if col==0:
            return self.OnGetItemKind(item)
        return 0

class TestFrame(wx.Frame):
    def __init__(self, parent, log):
        wx.Frame.__init__(self, parent, -1, "UltimateListCtrl in wx.LC_VIRTUAL mode", size=(700, 600))
        panel = wx.Panel(self, -1)
        sizer = wx.BoxSizer(wx.VERTICAL)
        listCtrl = TestUltimateListCtrl(panel, log)
        sizer.Add(listCtrl, 1, wx.EXPAND)
        panel.SetSizer(sizer)
        sizer.Layout()
        self.CenterOnScreen()
        self.Show()

if __name__ == '__main__':
    import sys
    app = wx.PySimpleApp()
    frame = TestFrame(None, sys.stdout)
    frame.Show(True)
    app.MainLoop()

Btw, following is the code I used to create the same thing without the VIRTUAL mode. And in this case, I can check the boxes beside the first column data in every row. But, I will be working with tens of thousands of items and I cannot rely on loading the items like below because it is very slow. Hence, I want to use the Virtual List, but I don't know how to get the same functionality in it.

import wx
import images
import random
import os, sys
from wx.lib.agw import ultimatelistctrl as ULC

class TestUltimateListCtrl(ULC.UltimateListCtrl):
    def __init__(self, parent, log):

        ULC.UltimateListCtrl.__init__(self, parent, -1, agwStyle=ULC.ULC_REPORT|ULC.ULC_SINGLE_SEL|ULC.ULC_VRULES|ULC.ULC_HRULES)

    self.table_fields=['First','Second','Third']
    field_index=0
        for field in self.table_fields:
        info = ULC.UltimateListItem()
        info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT | ULC.ULC_MASK_CHECK
        info._image = []
        info._format = wx.LIST_FORMAT_CENTER
        info._kind = 1      
        info._text = field
        info._font= wx.Font(13, wx.ROMAN, wx.NORMAL, wx.BOLD)
        self.InsertColumnInfo(field_index, info)
        self.SetColumnWidth(field_index,175)
        field_index += 1

    for record_index in range(0,1000):
        for field in self.table_fields:
        if self.table_fields.index(field)==0:
            self.InsertStringItem(record_index, 'Item %d, Column %d' % (record_index,self.table_fields.index(field)),it_kind=1)
        else:
            self.SetStringItem(record_index, self.table_fields.index(field), 'Item %d, Column %d' % (record_index,self.table_fields.index(field)))

class TestFrame(wx.Frame):
    def __init__(self, parent, log):
        wx.Frame.__init__(self, parent, -1, "UltimateListCtrl in wx.LC_VIRTUAL mode", size=(700, 600))
        panel = wx.Panel(self, -1)
        sizer = wx.BoxSizer(wx.VERTICAL)
        listCtrl = TestUltimateListCtrl(panel, log)
        sizer.Add(listCtrl, 1, wx.EXPAND)
        panel.SetSizer(sizer)
        sizer.Layout()
        self.CenterOnScreen()
        self.Show()

if __name__ == '__main__':
    import sys
    app = wx.PySimpleApp()
    frame = TestFrame(None, sys.stdout)
    frame.Show(True)
    app.MainLoop()

Solution

  • This question has been here for a while and I've been trying to find a solution to this problem around the web (to no avail). I've now solved mine and I'm posting here just in case someone might find the solution useful, or may have an more appropriate one.


    Short answer: you will need to manually keep track of checked and unchecked items. To detect which are being checked (clicked), you can bind to the EVT_LIST_ITEM_CHECKING event.

    Long answer: First, you will need a way to keep track of which items are checked or not. Then use that for determining what to return for OnGetItemColumnCheck. You could, for example, use a list of item+columns like so:

    def __init__(...):
        ...
        self.checked = []
        ...
    
    def OnGetItemColumnCheck(self, item, column):
        item_column = (item, column)
        if item_column in self.checked:
            return True
        else:
            return False
    

    You will now need a way to populate that list. To do that, you will need to bind to the EVT_LIST_ITEM_CHECKING and do the appropriate actions:

    def __init__(...):
        ...
        self.checked = []
        self.Bind(ULC.EVT_LIST_ITEM_CHECKING, self.OnCheck)
        ...
    
    def OnCheck(self, event):
        item_column = (event.m_itemIndex, event.m_item.GetColumn())
        try:
            idx = self.checked.index(item_column)
        except ValueError:
            idx = None
    
        if idx == None:
            self.checked.append(item_column)
        else:
            del(self.checked[idx])
        self.Refresh()
    

    The self.Refresh() call is essential as sometimes the checkbox won't get redrawn. After this, you should now be able to check and uncheck items (and that information is easily accessible to boot!). Here is your complete code with the above modifications:

    import wx
    import random
    import os, sys
    from wx.lib.agw import ultimatelistctrl as ULC
    
    class TestUltimateListCtrl(ULC.UltimateListCtrl):
        def __init__(self, parent, log):
            ULC.UltimateListCtrl.__init__(self, parent, -1, agwStyle=ULC.ULC_VIRTUAL|ULC.ULC_REPORT|ULC.ULC_SINGLE_SEL|ULC.ULC_VRULES|ULC.ULC_HRULES)
            self.SetItemCount(1000)
            self.table_fields=['First','Second','Third']
            field_index=0
            for field in self.table_fields:
                info = ULC.UltimateListItem()
                info._mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT | ULC.ULC_MASK_CHECK
                info._image = []
                info._format = wx.LIST_FORMAT_CENTER
                info._kind = 1
                info._text = field
                info._font= wx.Font(13, wx.ROMAN, wx.NORMAL, wx.BOLD)
                self.InsertColumnInfo(field_index, info)
                self.SetColumnWidth(field_index,175)
                field_index += 1
    
            self.checked = []
            self.Bind(ULC.EVT_LIST_ITEM_CHECKING, self.OnCheck)
    
        def OnCheck(self, event):
            item_column = (event.m_itemIndex, event.m_item.GetColumn())
            try:
                idx = self.checked.index(item_column)
            except ValueError:
                idx = None
    
            if idx == None:
                self.checked.append(item_column)
            else:
                del(self.checked[idx])
            self.Refresh()
    
        def getColumnText(self, index, col):
            item = self.GetItem(index, col)
            return item.GetText()
    
        def OnGetItemText(self, item, col):
            return "Item %d, Column %d" % (item,col)
    
        def OnGetItemColumnImage(self, item, col):
            return []
    
        def OnGetItemImage(self, item):
            return []
    
        def OnGetItemAttr(self, item):
            return None
    
        def OnGetItemTextColour(self, item, col):
            return None
    
        def OnGetItemToolTip(self, item, col):
            return None
    
        def OnGetItemKind(self, item):
            return 1
    
        def OnGetItemColumnKind(self, item, col):
            if col==0:
                return self.OnGetItemKind(item)
            return 0
    
        def OnGetItemColumnCheck(self, item, column):
            item_column = (item, column)
            if item_column in self.checked:
                return True
            else:
                return False
    
    class TestFrame(wx.Frame):
        def __init__(self, parent, log):
            wx.Frame.__init__(self, parent, -1, "UltimateListCtrl in wx.LC_VIRTUAL mode", size=(700, 600))
            panel = wx.Panel(self, -1)
            sizer = wx.BoxSizer(wx.VERTICAL)
            listCtrl = TestUltimateListCtrl(panel, log)
            sizer.Add(listCtrl, 1, wx.EXPAND)
            panel.SetSizer(sizer)
            sizer.Layout()
            self.CenterOnScreen()
            self.Show()
    
    if __name__ == '__main__':
        import sys
        app = wx.PySimpleApp()
        frame = TestFrame(None, sys.stdout)
        frame.Show(True)
        app.MainLoop()