Search code examples
pythonpython-3.xuser-interfacewxpython

wxPython - How to sort ListCtrl column items?


I am trying to create an CheckListCtrl where you could sort all the Data in an column by clicking on its header.

In the basic example of my code i will post below i setup "rows" as a List of Tuples because in my final version the ListCtrl will show the result of an SQLite Query.

The problem i have with my code so far:

I used self.itemDataMap = rows wrong i think, i get this error message if i try to sort: TypeError: list indices must be integers or slices, not tuple. So how do i use it with an List of Tuples and not with an Dictionary?

import wx
import wx.lib.mixins.listctrl as listmix
from wx.lib.agw import ultimatelistctrl as ULC

APPNAME='Sortable Ultimate List Ctrl'
APPVERSION='1.0'
MAIN_WIDTH=300
MAIN_HEIGHT=300

class TestUltimateListCtrlPanel(wx.Panel, listmix.ColumnSorterMixin):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS, size=(MAIN_WIDTH,MAIN_HEIGHT))

        self.index = 0

        self.list_ctrl = ULC.UltimateListCtrl(self, -1, agwStyle=ULC.ULC_REPORT|ULC.ULC_HAS_VARIABLE_ROW_HEIGHT)
        self.list_ctrl.InsertColumn(0, "Make")
        self.list_ctrl.InsertColumn(1, "Model")
        self.list_ctrl.InsertColumn(2, "Year")
        self.list_ctrl.InsertColumn(3, "Color")

        rows = [("Ford", "Taurus", "1996", "Blue"),
                ("Nissan", "370Z", "2010", "Green"),
                ("Porche", "911", "2009", "Red")
                ]

        index = 0
        for data in rows:
            pos=self.list_ctrl.InsertStringItem(index, data[0])
            self.list_ctrl.SetStringItem(index, 1, data[1])
            self.list_ctrl.SetStringItem(index, 2, data[2])
            self.list_ctrl.SetStringItem(index, 3, data[3])

            self.list_ctrl.SetItemData(index, rows[index])

            index += 1

        self.itemDataMap = rows

        listmix.ColumnSorterMixin.__init__(self, 3)
        self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list_ctrl)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.list_ctrl, 1, wx.ALL|wx.EXPAND, 5)
        self.SetSizer(sizer)

    def GetListCtrl(self):
        return self.list_ctrl

    def OnColClick(self, event):
        pass

class MyForm(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self,None,wx.ID_ANY,'%s v%s' % (APPNAME,APPVERSION),size=(MAIN_WIDTH,MAIN_HEIGHT),style=wx.MINIMIZE_BOX | wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN)
        panel = TestUltimateListCtrlPanel(self)

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

Solution

  • First let me quote the documentation of wx.lib.mixins.listctrl.ColumnSorterMixin:

    The combined class must have an attribute named itemDataMap that is a dictionary mapping the data values to a sequence of objects representing the values in each column. These values are compared in the column sorter to determine sort order.

    That's hardly understandable.

    What it means is, that .itemDataMap hs to be a dictionary, where the key of each entry are the data of a row. The value is a list:

    self.itemDataMap = {}
    for rowIndex, data in enumerate(rows):
        self.itemDataMap[data] = []
    

    Each element of the immer list is associated to a column and is used to sort the elements of a column. If the rows should be sorted in alphabetic order dependent of the values of a column, then the value which is associated to the column index (in the dictionary of a row) can be the value of the filed:

    self.itemDataMap[data] = []
    for coldata in data:
        self.itemDataMap[data] += coldata
    

    Since the rows are already organised in a list, the rows can be use directly:

    self.itemDataMap[data] = data
    

    The same can be achieved by

    self.itemDataMap = {data : data for data in rows} 
    

    Note, the keys of .itemDataMap have to correspond to the data of a row, which is set by SetItemData().

    Since the data of a row are organised in a list

    When the list should be sorted by the values of specific column index col, then all then elements in .itemDataMap which are associated to col are listed and the list is sorted by this elements. You can imagine this like:

    col = ... # integral index of the column 
    sorted( [values[col] for values in self.itemDataMap.values()] )
    

    Further note, the number of columns is 4:

    listmix.ColumnSorterMixin.__init__(self, 3)
    listmix.ColumnSorterMixin.__init__(self, 4)


    Class TestUltimateListCtrlPanel:

    class TestUltimateListCtrlPanel(wx.Panel, listmix.ColumnSorterMixin):
        def __init__(self, parent):
            wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS, size=(MAIN_WIDTH,MAIN_HEIGHT))
    
            self.list_ctrl = ULC.UltimateListCtrl(self, -1, agwStyle=ULC.ULC_REPORT|ULC.ULC_HAS_VARIABLE_ROW_HEIGHT)
            self.list_ctrl.InsertColumn(0, "Make")
            self.list_ctrl.InsertColumn(1, "Model")
            self.list_ctrl.InsertColumn(2, "Year")
            self.list_ctrl.InsertColumn(3, "Color")
    
            rows = [("Ford", "Taurus", "1996", "Blue"),
                    ("Nissan", "370Z", "2010", "Green"),
                    ("Porche", "911", "2009", "Red")
                    ]
    
            for rowIndex, data in enumerate(rows):
                for colIndex, coldata in enumerate(data):
                    if colIndex == 0:
                        self.list_ctrl.InsertStringItem(rowIndex, coldata)
                    else:
                        self.list_ctrl.SetStringItem(rowIndex, colIndex, coldata)
                self.list_ctrl.SetItemData(rowIndex, data)
    
            self.itemDataMap = {data : data for data in rows} 
    
            listmix.ColumnSorterMixin.__init__(self, 4)
            self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list_ctrl)
    
            sizer = wx.BoxSizer(wx.VERTICAL)
            sizer.Add(self.list_ctrl, 1, wx.ALL|wx.EXPAND, 5)
            self.SetSizer(sizer)
    
        def GetListCtrl(self):
            return self.list_ctrl
    
        def OnColClick(self, event):
            pass