Problems with sorting a listctrl

Hello,

I had struggled for hour.
So I decided to take the listctrl from demo,
modify it and try again; the same error.

If you click on the first column header,
the sorting is ok.
Several times clicking on it: sort ascending, descending ... ok.

Then clicking on the second column header.
The first time it seems ok, but
if you click here several times, the sorting gets confused.
You see it especially on the red line, which moves almost
randomly.

Please no comment about the programming style,
it was a rush job :wink:

What did I make wrong?

Many thanks!

#-------begin sample code

import wx

musicdata = {
1 : ("Bad English", "The Price Of Love", "Rock"),
2 : ("DNA featuring Suzanne Vega", "Tom's Diner", "Rock"),
3 : ("George Michael", "Praying For Time", "Rock"),
4 : ("Gloria Estefan", "Here We Are", "Rock"),
5 : ("Linda Ronstadt", "Don't Know Much", "Rock"),
6 : ("Michael Bolton", "How Am I Supposed To Live Without You",
"Blues"),
7 : ("Paul Young", "Oh Girl", "Rock"),
8 : ("Paula Abdul", "Opposites Attract", "Rock"),
9 : ("Richard Marx", "Should've Known Better", "Rock"),
}

class TestListCtrl(wx.ListCtrl):
  def __init__(self, parent, ID, pos=wx.DefaultPosition,
         size=wx.DefaultSize, style=0):
    wx.ListCtrl.__init__(self, parent, ID, pos, size, style)

class TestListCtrlFrame(wx.Frame):
  def __init__(self, parent, id, title):
    wx.Frame.__init__(self, parent, id, title)
    panel = TestListCtrlPanel1 (self)

class TestListCtrlPanel1(wx.Panel):
  def __init__(self, parent):
    wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS)

    tID = wx.NewId()

    self.list = TestListCtrl(self, tID,
                 style=wx.LC_REPORT
                 > wx.SUNKEN_BORDER
                 )

    self.PopulateList()

    self.itemDataMap = musicdata
    self.oldsortcolumn = -1
    self.sort_asc = True

    self.Bind(wx.EVT_LIST_COL_CLICK, self.OnHeaderClick, self.list)
    self.Bind(wx.EVT_SIZE, self.OnSize)

  def PopulateList(self):
    # but since we want images on the column header we have to do it the
hard way:
    info = wx.ListItem()
    info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE |
wx.LIST_MASK_FORMAT
    info.m_image = -1
    info.m_format = 0
    info.m_text = "Artist"
    self.list.InsertColumnInfo(0, info)

    info.m_format = wx.LIST_FORMAT_RIGHT
    info.m_text = "Title"
    self.list.InsertColumnInfo(1, info)

    info.m_format = 0
    info.m_text = "Genre"
    self.list.InsertColumnInfo(2, info)

    items = musicdata.items()
    for x in range(len(items)):
      key, data = items[x]
      self.list.InsertStringItem(x, data[0])
      self.list.SetStringItem(x, 1, data[1])
      self.list.SetStringItem(x, 2, data[2])
      self.list.SetItemData(x, x)

    self.list.SetColumnWidth(0, wx.LIST_AUTOSIZE)
    self.list.SetColumnWidth(1, wx.LIST_AUTOSIZE)
    self.list.SetColumnWidth(2, 100)

    # show how to select an item
    self.list.SetItemState(5, wx.LIST_STATE_SELECTED,
wx.LIST_STATE_SELECTED)

    # show how to change the colour of a couple items
    item = self.list.GetItem(1)
    item.SetTextColour(wx.BLUE)
    self.list.SetItem(item)
    item = self.list.GetItem(4)
    item.SetTextColour(wx.RED)
    self.list.SetItem(item)

    self.currentItem = 0

  def OnHeaderClick(self, event):

    self.nCol = event.GetColumn()
    self.SortHeader()
    event.Skip()
    
  def SortHeader(self):
    if self.oldsortcolumn != self.nCol:
      self.sort_asc = True
      self.oldsortcolumn = self.nCol
    else:
      self.sort_asc = not self.sort_asc
    self.list.SortItems(self.columnSorter)
    
    #nItem = self.ResultList.GetNextItem(-1, wx.LIST_NEXT_ALL,
wx.LIST_STATE_SELECTED)
    #self.ResultList.EnsureVisible(nItem)

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

  def columnSorter(self, key1, key2):
    if not self.sort_asc:
      key1, key2 = key2, key1
    item1 = self.getColumnText (key1, self.nCol)#.lower()
    item2 = self.getColumnText (key2, self.nCol)#.lower()
    if item1 == item2:
      return 0
    elif item1 < item2:
      return -1
    else:
      return 1

  def OnSize(self, event):
    w,h = self.GetClientSizeTuple()
    self.list.SetDimensions(0, 0, w, h)

class MyFrame(wx.Frame):
  def __init__(self, parent, id, title):
    wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition,
wx.DefaultSize, wx.DEFAULT_DIALOG_STYLE | wx.MAXIMIZE_BOX |
wx.THICK_FRAME | wx.RESIZE_BORDER)
    #self.Panel = TestListCtrlFrane(self, 102, "")
    #self.Panel.Show()
    self.Panel = TestListCtrlPanel1(self)
    
class DrApp(wx.App):

  def OnInit(self):
    
    frame = MyFrame(None, 101, "MyFrame")
    frame.Show(True)
    self.SetTopWindow(frame)
    return True
  
if __name__ == '__main__':
  app = DrApp(0)
  app.MainLoop()

#end sample code

···

--
Franz Steinhäusler

DrPython (Project Developer)
http://drpython.sourceforge.net/
http://sourceforge.net/projects/drpython/

first... please try and attach the .py file to the email... don't just copy&paste the code... it took some time to get your code to run.

now for the fix.

Decide how do you want your data sorted... if you want to use the wx.lib.mixins.listctrl.ColumnSorterMixin read carefully what does it needs (itemDataMap, GetListCtrl)
If you want to implement your own sorter bare in mind that you do not receive keys in your sorter but data.
excerpt from wx Docs:

Parameters:
item1: client data associated with the first item (NOT the index).
item2: client data associated with the second item (NOT the index).

see the "NOT the index" part... there is your problem. (Honest mistake, I missed that too :slight_smile: )

now for the compare method...
if you want a simple toggle asc/desc sort you might implement it like

     def sorter(self, d1, d2):
         return self.sort_asc*cmp(self.getColumnText(d1, self.col), self.getColumnText(d2, self.col))

     def OnColClick(self, event):
         self.col = event.GetColumn()
         self.asc *= -1
         self.list.SortItems(self.sorter)
         event.Skip()

and in your case
     def getColumnText(self, key, col):
         return musicdata[key][col]

I hope that helps :slight_smile:

···

On Fri, 08 Oct 2004 11:37:48 +0200, Franz Steinhäusler <franz.steinhaeusler@gmx.at> wrote:

Hello,

I had struggled for hour.
So I decided to take the listctrl from demo,
modify it and try again; the same error.

--
Peter Damoc
Hacker Wannabe

[...]

Hello Peter,

first... please try and attach the .py file to the email... don't just
copy&paste the code... it took some time to get your code to run.

First, thank you for your prompt reply!

I'm sorry, I thougt, it wasn't to long,
BUT i forgot, that my newsreader inserts a linebreak automatically.
I haven't thought on attaching the file. Next time :wink:

now for the fix.

Decide how do you want your data sorted... if you want to use the
wx.lib.mixins.listctrl.ColumnSorterMixin read carefully what does it needs
(itemDataMap, GetListCtrl)

The matter is, that for the sorting, some column need to be converted specially,
before they are ready to compare.

If you want to implement your own sorter bare in mind that you do not
receive keys in your sorter but data.
excerpt from wx Docs:

Parameters:
item1: client data associated with the first item (NOT the index).
item2: client data associated with the second item (NOT the index).

see the "NOT the index" part... there is your problem. (Honest mistake, I
missed that too :slight_smile: )

now for the compare method...
if you want a simple toggle asc/desc sort you might implement it like

    def sorter(self, d1, d2):
        return self.sort_asc*cmp(self.getColumnText(d1, self.col),
self.getColumnText(d2, self.col))

    def OnColClick(self, event):
        self.col = event.GetColumn()
        self.asc *= -1
        self.list.SortItems(self.sorter)
        event.Skip()

and in your case
    def getColumnText(self, key, col):
        return musicdata[key][col]

I hope that helps :slight_smile:

Of cource, very helpful indeed, thank you very much and also for
the other hints.

I watched the output in getColumnText, and it looked ok, so I didn't focus on that;
otherwise I would be earlier on the clue, that this wasn't as expected.

I was convinced, the failure laid on the art of storing the data into the listctrl or
had to do something with the SetItemState.

···

On Fri, 08 Oct 2004 14:22:21 +0300, "Peter Damoc" <pdamoc@gmx.net> wrote:

--
Franz Steinhäusler

DrPython (Project Developer)
http://drpython.sourceforge.net/