Can you send a runnable demo? I was just dealing with the same issue
yesterday and it was because I had forgotten to set a unique value for
the call to SetItemData. I originally thought that might be the case
in your SortedList as well, but looking through refreshList it doesseem that index will always have a value because it always either hits
the colNum==0 or the else condition. (Unless data has no elements,
but then you’d get an error because index wouldn’t be defined…)Could it be that you need to use the GetSecondarySortValues method?
Rob
It gets weirder and weirder. It turns out that the difference isn’t actually between the two classes at all, but in how I was deploying them (yes, I realize I should have figured that out long before!)
My apps that use the SortedCheckList are all on panels - i.e., I have a panel on the left with buttons and options, and the right panel contains my list. My (one big) app that uses the SortedList actually creates 10 pages on a Notebook, each containing a SortedList.
So when I started to make a demo to send, I began with the Notebook app, and I was just going to create one page using the SortedList and one using the SortedCheckList - lo and behold, neither of them works! I re-jiggered it to create two panels instead, and they both work…well, sorta. Now, when I click on the last column header (“V”), I get the following error:
Traceback (most recent call last):
File “C:\Python25\lib\site-packages\wx-2.8-msw-unicode\wx\lib\mixins\listctrl.py”, line 118, in __OnColClick
self._colSortFlag[col] = int(not self._colSortFlag[col])
IndexError: list index out of range
which I fixed by editing the ColumnSorterMixin and changing
def SetColumnCount(self, newNumColumns):
self._colSortFlag = [0] * (newNumColumns)
self._col = -1
to
def SetColumnCount(self, newNumColumns):
self._colSortFlag = [0] * (newNumColumns + 1)
self._col = -1
at line 69 of listctrl.py.
So now I have a few more questions…
-
What the heck is the difference between a notebook page and a panel? (I realize there are LOTS of differences, but you know what I mean.)
-
Has anybody else run into the last-column error?
-
Does my change to the ColumnSorterMixin break anybody’s code? If not, should I submit a bug report? (I find it much more likely that I screwed up somehow than that I found a bug in mature code, but stranger things have happened…)
Here’s my demo - to switch back and forth between notebook and panel, comment or un-comment “Option 1” or “Option 2” in myFrame…
import wx, string, sys, os, copy
from types import *
from wx.lib.mixins.listctrl import ColumnSorterMixin, CheckListCtrlMixin, ListCtrlAutoWidthMixin
DemoList = [[“Column1”, “Column2”, “Col3”, “4”, “V”],
[“How”, “are”, “we”, “doing”, “here?”],
["Things", "aren't", "looking", "too", "good."],
["But", "they", "could", "get", "better."],
["I", "hope", "this", "list", "works"],
["How", "are", "you", "doing", "there?"]]
SortList = copy.deepcopy(DemoList)
ChkList = copy.deepcopy(DemoList)
class SortedList(wx.ListCtrl, ColumnSorterMixin, ListCtrlAutoWidthMixin):
‘’’
Modularize management of columns and headers. Unfortunately this adds some constraints:
- If the data structure is a dictionary:
- it must have a numeric index
- key 0 must correspond to the header record
- If the data structure is a list:
- the first item in the list must be the header record
One drawback of making column creation automatic is that column width and style are set to defaults.
Future versions may add more control...
'''
def __init__(self, parent, inThing, log=None):
wx.ListCtrl.__init__(self, parent, -1, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.LC_REPORT
> wx.BORDER_NONE
> wx.WANTS_CHARS
)
ListCtrlAutoWidthMixin.__init__(self)
self.log = log
header = []
if isinstance(inThing, DictType): # inThing is a dictionary
if inThing.has_key(0):
header = inThing[0]
del inThing[0]
self.thing = inThing
elif isinstance(inThing, ListType): #inThing is a list, so make a dictionary out of it
header = inThing[0]
del inThing[0]
self.thing = {}
for num, item in enumerate(inThing):
self.thing[num+1] = item # needs to be 1-based, not 0-based
ColumnSorterMixin.__init__(self, len(self.thing)) # number of columns equals width of header record
self.itemDataMap = self.thing
self.Widths = []
for colNum, colData in enumerate(header):
self.InsertColumn(colNum, colData)
self.Widths.append(len(colData))
self.refreshList(init=True)
dc = wx.ScreenDC()
for colNum, colWidth in enumerate(self.Widths
):
width, h = dc.GetTextExtent(‘N’*colWidth) # capital W is too wide, capital I too narrow
self.SetColumnWidth(colNum, width)
def getColumnText(self, index, col):
item = self.GetItem(index, col)
return item.GetText()
def refreshList(self, init = False):
for key, data in self.thing.items():
for colNum, colData in enumerate(data):
if init: # first time only - otherwise just update existing items
self.Widths[colNum] = max(self.Widths[colNum], len(colData), 5) # get max length for each column, at least 5
if (colNum==0):
index = self.InsertStringItem(sys.maxint, data[0])
self.tmpFont = self.GetItemFont(index)
else:
index = key -1
self.SetStringItem(index, colNum, colData)
self.SetItemData(index, key)
def GetListCtrl(self):
return self
class SortedCheckList(wx.ListCtrl, CheckListCtrlMixin, ColumnSorterMixin, ListCtrlAutoWidthMixin):
'''
Combines the CheckListCtrl and ColumnSorter mixins for the ListCtrl widget.
Also modularizes management of columns and headers. Unfortunately this adds some constraints:
- If the data structure is a dictionary:
- it must have a numeric index
- key 0 must correspond to the header record
- If the data structure is a list:
- the first item in the list must be the header record
One drawback of making column creation automatic is that column width and style are set to defaults.
Future versions may add more control...
'''
def __init__(self, parent, inThing, log=None):
self.log = log
header = []
if isinstance(inThing, DictType): # inThing is a dictionary
if inThing.has_key(0):
header = inThing[0]
del inThing[0]
self.thing = inThing
elif isinstance(inThing, ListType):
header = inThing[0]
del inThing[0]
self.thing = {}
for num, item in enumerate(inThing):
self.thing[num+1] = item # needs to be 1-based, not 0-based
wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT)
CheckListCtrlMixin.__init__(self)
ListCtrlAutoWidthMixin.__init__(self)
ColumnSorterMixin.__init__(self, len(self.thing))
self.itemDataMap = self.thing
Widths = []
for colNum, colData in enumerate(header):
self.InsertColumn(colNum, colData)
Widths.append(len(colData))
for key, data in self.thing.items():
for colNum, colData in enumerate(data):
Widths[colNum] = max(Widths[colNum], len(str(colData)), 5) # get max length for each column, at least 5
if (colNum==0):
index = self.InsertStringItem(sys.maxint, data[0])
tmpFont = self.GetItemFont(index)
else:
self.SetStringItem
(index, colNum, str(colData))
self.SetItemData(index, key)
dc = wx.ScreenDC()
dc.SetFont(tmpFont)
for colNum, colWidth in enumerate(Widths):
width, h = dc.GetTextExtent
(‘N’*colWidth) # capital W is too wide, capital I too narrow
self.SetColumnWidth(colNum, width)
# for wxMSW
self.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnRightClick
)
# for wxGTK
self.Bind(wx.EVT_RIGHT_UP, self.OnRightClick)
def GetListCtrl(self):
return self
def OnRightClick(self, event):
# only do this part the first time so the events are only bound once
if not hasattr(self, "popupID1"):
self.popupID1 = wx.NewId()
self.popupID2 = wx.NewId()
self.popupID3 = wx.NewId()
self.popupID4 = wx.NewId
()
self.Bind(wx.EVT_MENU, self.OnPopupOne, id=self.popupID1)
self.Bind(wx.EVT_MENU, self.OnPopupTwo, id=self.popupID2)
self.Bind(wx.EVT_MENU, self.OnPopupThree, id=self.popupID3
)
self.Bind(wx.EVT_MENU, self.OnPopupFour, id=self.popupID4)
# make a menu
menu = wx.Menu()
# add some items
menu.Append(self.popupID1, "Select highlighted items")
menu.Append(self.popupID2, "Deselect highlighted items")
menu.Append(self.popupID3, "Select all")
menu.Append(self.popupID4, "Deselect all")
# Popup the menu. If an item is selected then its handler
# will be called before PopupMenu returns.
self.PopupMenu(menu)
menu.Destroy()
def OnPopupOne(self, event):
index = self.GetFirstSelected()
while index != -1:
self.CheckItem(index)
index = self.GetNextSelected(index)
def OnPopupTwo(self, event):
index = self.GetFirstSelected()
while index != -1:
self.CheckItem
(index, False)
index = self.GetNextSelected(index)
def OnPopupThree(self, event):
num = self.GetItemCount()
for i in range(num):
self.CheckItem(i)
def OnPopupFour(self, event):
num = self.GetItemCount()
for i in range(num):
self.CheckItem(i, False)
class Log(object):
“”"Log output is redirected to the status bar of the containing frame.
"""
def WriteText(self,text_string):
self.write(text_string)
def write(self,text_string):
wx.GetApp().GetTopWindow().SetStatusText(text_string)
class myNB(
wx.Notebook):
···
On Dec 18, 2007 8:50 AM, Rob McMullen rob.mcmullen@gmail.com wrote:
#----------------------------------------------------------------------
The guts of the main operation…
#----------------------------------------------------------------------
def init(self, parent, id, log):
wx.Notebook.__init__(self, parent, id, size=wx.DefaultSize, style=wx.BK_DEFAULT)
self.log = log
pageCheck = SortedCheckList(self,ChkList)
self.AddPage(pageCheck, "SortedCheckList")
pageSort = SortedList(self,SortList)
self.AddPage(pageSort, "SortedList")
self.Show(True)
class myFrame(wx.Frame):
def init(self, parent, id, title, size, style = wx.DEFAULT_FRAME_STYLE ):
wx.Frame.init(self, parent, id, title, size=size, style=style)
self.CreateStatusBar(1)
log=Log()
#====Option 1======
self.win = myNB(self, -1, log)
#====Option 2======
"""
panel = wx.Panel(self, -1)
hbox = wx.BoxSizer(wx.HORIZONTAL)
lvbox = wx.BoxSizer(wx.VERTICAL)
rvbox = wx.BoxSizer
(wx.VERTICAL)
leftPanel = wx.Panel(panel, -1)
rightPanel = wx.Panel(panel, -1)
self.Llist = SortedCheckList(leftPanel, SortList)
self.Rlist = SortedList(rightPanel, ChkList)
leftPanel.SetSizer(lvbox)
lvbox.Add(self.Llist, 1, wx.EXPAND | wx.TOP, 3)
rightPanel.SetSizer(rvbox)
rvbox.Add(self.Rlist, 1, wx.EXPAND | wx.TOP, 3)
hbox.Add(leftPanel, 1, wx.EXPAND)
hbox.Add(rightPanel, 1, wx.EXPAND)
panel.SetSizer(hbox)
self.Centre()
self.Show(True)
"""
#====end Option 2======
class MyApp(
wx.App):
def OnInit(self):
f = myFrame(None, -1, “Stripped-down Demo of SortedList”,wx.Size(700,600))
f.Show()
return True
#===============================================================================================
Where it all begins…
#===============================================================================================
def main(argv=None):
if argv is None:
argv = sys.argv
app = MyApp(0)
app.MainLoop()
if name == ‘main’:
main()