I have used wxpython for years (decades?), but have only just started using wx.ListCtrl. I have a working example.
Now I want to be able to sort by a column when the user clicks on a column header (or column), and I am finding the attempt incredibly frustrating. I don’t understand what the class documentation says about sorting (what is a “sort indicator”? - this term is never defined, nor what you are supposed to do with it). I have tried several examples on the web (most/all use a mixin) but they are all incomplete or broken.
Please, please, please, could someone give me a minimal (but complete) working program that creates a ListCtrl (2 rows and 2 columns is fine) and demonstrates how to sort by one column or another (I don’t care how the sorting is triggered, whatever is easiest).
I am using wxpython 4.2.3 and python 3.13.3 on Windows 11.
Thanks in advance,
Rob Cliffe
I would suggest looking at the wxPython demo, and in particular at the ListCtrl sample. It shows the use of ColumnSorterMixin nicely and it’s not a particularly complicated demo.
Thanks Andrea. Could you give me a link to the web page, please.
Rob
You can download the wxPython demo for v4.2.3 from Index of /wxPython4/extras/4.2.3 - the file you want is: wxPython-demo-4.2.3.tar.gz
When you unpack that file, you will get a wxPython-demo-4.2.3 folder.
Inside that folder there is a demo sub-folder.
To start the demo application, run the demo.py python script from that sub-folder.
Here is a simple example:
import sys
import wx
from wx.lib.mixins.listctrl import ColumnSorterMixin
AIRFIELDS = (
("EGBB", "Birmingham"),
("EGCC", "Manchester"),
("EGKB", "Biggin Hill"),
("EGKK", "Gatwick"),
("EGLC", "London City"),
("EGLF", "Farnborough"),
("EGLL", "Heathrow"),
("EGSS", "Stansted"),
)
COLUMN_SETTINGS = (
("ICAO", 100),
("Airfield", 160)
)
NUM_COLUMNS = len(COLUMN_SETTINGS)
class MyFrame(wx.Frame, ColumnSorterMixin):
def __init__(self, parent, id=wx.ID_ANY):
super().__init__(parent, id, "Click headings to sort")
self.SetSize((400, 300))
main_sizer = wx.BoxSizer(wx.VERTICAL)
style = wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES
self.list_ctrl = wx.ListCtrl(self, wx.ID_ANY, style=style)
ColumnSorterMixin.__init__(self, NUM_COLUMNS)
for i, (heading, width) in enumerate(COLUMN_SETTINGS):
self.list_ctrl.InsertColumn(i, heading, format=wx.LIST_FORMAT_CENTRE)
self.list_ctrl.SetColumnWidth(i, width)
self.itemDataMap = self.getDataDict()
for key, (icao, airfield) in self.itemDataMap.items():
index = self.list_ctrl.InsertItem(sys.maxsize, icao)
self.list_ctrl.SetItem(index, 1, airfield)
# This is needed by ColumnSorterMixin
self.list_ctrl.SetItemData(index, key)
main_sizer.Add(self.list_ctrl, 1, wx.EXPAND, 0)
self.SetSizer(main_sizer)
self.Layout()
self.image_list = wx.ImageList(16, 16)
self.down_arrow = self.image_list.Add(wx.Bitmap("arrow-down.png", wx.BITMAP_TYPE_PNG))
self.up_arrow = self.image_list.Add(wx.Bitmap("arrow-up.png", wx.BITMAP_TYPE_PNG))
self.list_ctrl.SetImageList(self.image_list, wx.IMAGE_LIST_SMALL)
@staticmethod
def getDataDict():
data_dict = {}
for i, item in enumerate(AIRFIELDS):
data_dict[i] = item
return data_dict
def GetListCtrl(self):
return self.list_ctrl
def GetSortImages(self):
return self.up_arrow, self.down_arrow
if __name__ == "__main__":
app = wx.App()
frame = MyFrame(None, wx.ID_ANY)
frame.Show()
app.MainLoop()
You will need these icons:
icons.zip (1.2 KB)
Tested using: wxPython 4.2.5 gtk3 (phoenix) wxWidgets 3.2.9 + Python 3.12.3 + Linux Mint 22.3
Thanks for doing this (and testing it), Richard. That ought to be just what I need.
Unfortunately, when I run it on my platform (as per my original post) I get:
Traceback (most recent call last):
File “R:\SimpleExample.py”, line 75, in
frame = MyFrame(None, wx.ID_ANY)
File “R:\SimpleExample.py”, line 42, in init
index = self.list_ctrl.InsertItem(sys.maxsize, icao)
wx._core.wxAssertionError: C++ assertion “info.m_itemId != -1” failed at …..\src\msw\listctrl.cpp(2001) in wxListCtrl::InsertItem(): Item ID must be set.
Rob
Perhaps windows doesn’t like being passed sys.maxsize?
A possible alternative for that for-loop:
for key, (icao, airfield) in self.itemDataMap.items():
self.list_ctrl.Append((icao, airfield))
# This is needed by ColumnSorterMixin
self.list_ctrl.SetItemData(key, key)
Bingo, Richard! That works for me. Thanks.
Having said which, a dreadful thought occurs to me: I may have been (mostly) wasting everybody’s time:
I want a virtual ListCtrl. I now guess it is NOT POSSIBLE to sort a VIRTUAL ListCtrl (this seems obvious when I think about how it might (not) work).
So what I should do is sort my data, then use RefreshItems(), along with EnsureVisible() and Focus(), as appropriate (a quick test indicated that this will work).
Duh! Why didn’t I think of it before? I suppose because I unthinkingly assumed ListCtrl would do it for me.
Maybe the moral is “When asking a question, show your code”.
I didn’t do that because it wasn’t particularly short and I didn’t want to force anyone to plough through it, to work out how it should be changed.
At least this discussion forced me to think harder about what I was trying to achieve. Sometimes just talking through your problem with others helps you see the light.
Anyway, apologies and thanks again.
Rob
