Skip to content

Instantly share code, notes, and snippets.

@Bouni
Last active February 8, 2023 15:15
Show Gist options
  • Select an option

  • Save Bouni/d89f45cdd5533ba0e228822827a49c48 to your computer and use it in GitHub Desktop.

Select an option

Save Bouni/d89f45cdd5533ba0e228822827a49c48 to your computer and use it in GitHub Desktop.
Allow for natural soting of wx ListCtrl data
# --------------------------------------------------------------------------------------
# Original code from the exccelent Mouse vs Python
# https://www.blog.pythonlibrary.org/2011/01/04/wxpython-wx-listctrl-tips-and-tricks/
# --------------------------------------------------------------------------------------
import re
import wx
import wx.lib.mixins.listctrl as listmix
import logging
LOGGER = logging.getLogger()
# https://wiki.wxpython.org/How%20to%20create%20a%20list%20control%20%28info%29
partdata = {
0: ("C1", "33P", "C_0603_1608Metric", "C1663", "187154"),
1: ("C2", "2.2u", "C_1206_3216Metric", "C50254", "22975"),
2: ("C3", "10n", "C_0603_1608Metric", "C57112", "536073"),
3: ("C4", "10u", "C_0603_1608Metric", "C19702", "879880"),
4: ("D1", "LTE-4206", "LED_D3.0mm_Clear", "", ""),
5: ("F1", "1A", "Fuse_1206_3216Metric", "C369159", "28290"),
6: ("FB1", "600 @ 600MHz", "L_0805_2012Metric", "C1017", "53369"),
7: ("J1", "Conn_01x01_Female", "Pin_D1.0mm_L10.0mm", "", ""),
8: ("J2", "Conn_01x01_Female", "Pin_D1.0mm_L10.0mm", "", ""),
9: ("L1", "15uH", "L_Sumida_CDMC6D28_7.25x6.5mm", "C182182", "2510"),
10: ("Q1", "SFH 309 FA-5/6", "LED_D3.0mm_IRBlack", "", ""),
11: ("Q2", "AO3400A", "SOT-23", "C20917", "14806"),
12: ("R1", "10k", "R_0603_1608Metric", "C25804", "5135918"),
13: ("R2", "910", "R_0603_1608Metric", "C351623", "3300"),
14: ("R3", "100", "R_0603_1608Metric", "C22775", "802960"),
15: ("R4", "100", "R_0603_1608Metric", "C22775", "802960"),
16: ("R5", "10k", "R_0603_1608Metric", "C25804", "5135918"),
17: ("R7", "100k", "R_0603_1608Metric", "C25803", "765889"),
18: ("R8", "18k", "R_0603_1608Metric", "C25961", "6002"),
19: ("R9", "750", "R_0603_1608Metric", "C23241", "25248"),
20: ("R10", "750", "R_0603_1608Metric", "C23241", "25248"),
21: ("U1", "mh-et-live-minikit", "MH-ET LIVE MiniKit", "", ""),
22: ("U2", "LMR54410", "SOT-23-6", "", "2484"),
23: ("U3", "LMV7235M7X", "SOT-353_SC-70-5", "C202268", "385"),
}
class NaturalSortListCtrl(wx.ListCtrl, listmix.ColumnSorterMixin):
def __init__(self, parent, size, columns=[]):
wx.ListCtrl.__init__(self, parent, size=size)
self.list_ctrl = wx.ListCtrl(
self,
size=size,
style=wx.LC_REPORT | wx.LC_SORT_ASCENDING,
)
listmix.ColumnSorterMixin.__init__(self, len(columns))
for index, column in enumerate(columns):
heading = column.get("heading")
fmt = column.get("format", wx.LIST_FORMAT_LEFT)
width = column.get("width", wx.LIST_AUTOSIZE)
self.list_ctrl.InsertColumn(index, heading=heading, format=fmt, width=width)
def SetData(self, data):
self.itemDataMap = data
for index, value in data.items():
self.list_ctrl.InsertItem(index, index)
for col, val in enumerate(value):
self.list_ctrl.SetItem(index, col+1, val) # +1 because of Dummy column, hack to make it work
self.list_ctrl.SetItemData(index, index)
def UpdateData(self, index, value):
self.itemDataMap[index] = value
# for col, data in enumerate(value):
# self.list_ctrl.SetItem(index, col+1, data)
self.list_ctrl.SetItemData(index, index)
def GetListCtrl(self):
return self.list_ctrl
def GetColumnSorter(self):
"""Returns a callable object to be used for comparing column values when sorting."""
return self.NaturalColumnSorter
def NaturalColumnSorter(self, key1, key2):
ascending = self._colSortFlag[self._col]
a = self.itemDataMap[key1][self._col-1] # -1 because of Dummy column, hack to make it work
b = self.itemDataMap[key2][self._col-1] # -1 because of Dummy column, hack to make it work
if a == b:
return 0
def convert(text):
return int(text) if text.isdigit() else text.lower()
def alphanum_key(key):
return [convert(c) for c in re.split("([0-9]+)", key)]
natorder = sorted([a, b], key=alphanum_key, reverse=bool(ascending))
return -1 if natorder.index(a) == 0 else 1
class Main(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(
self,
parent,
id=wx.ID_ANY,
title="Natural sort",
pos=wx.DefaultPosition,
size=wx.Size(800, 600),
style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER | wx.MAXIMIZE_BOX,
)
panel = wx.Panel(self)
columns = [
{"heading": "ID", "width": 0}, # Dummy column, hack to make it work
{"heading": "Reference"},
{"heading": "Value"},
{"heading": "Footprint"},
{"heading": "LCSC Part Number"},
{"heading": "Stock", "format": wx.LIST_FORMAT_CENTER, "width": 300},
]
self.list_ctrl = NaturalSortListCtrl(panel, size=(800, 300), columns=columns)
self.list_ctrl.SetData(partdata)
self.update_button = wx.Button(
panel, wx.ID_ANY, "Update", wx.DefaultPosition, (175, 38), 0
)
self.update_button.Bind(wx.EVT_BUTTON, self.update)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_ctrl, 0, wx.ALL | wx.EXPAND, 5)
sizer.Add(self.update_button, 0, wx.ALL | wx.EXPAND, 5)
panel.SetSizer(sizer)
self.Layout()
self.Centre(wx.BOTH)
def update(self, evt):
self.list_ctrl.UpdateData(
20, ("R100", "66P", "C_0603_1608Metric", "C1663", "187154")
)
if __name__ == "__main__":
app = wx.App(False)
frame = Main(None)
frame.Show()
app.MainLoop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment