Sorting in a wxListCtrl?

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
Luke727
Earned some good credits
Earned some good credits
Posts: 130
Joined: Wed Dec 22, 2004 9:31 am

Sorting in a wxListCtrl?

Post by Luke727 »

What I'm trying to do is sort items in a wxListCtrl (wxLC_REPORT flag set) based on certain attributes. I've got 6 or 7 columns showing various details (something like Artist, Album, Song Title for MP3s, etc). I tried the built-in sorting functionality but couldn't figure out how to make it work. Specifically, I couldn't make the wxListCtrlCompare() callback function a member of my wxListCtrl-derived class so I couldn't access the items in the list (I could only access the client data associated with the item, which is useless in my case). Is there a decent way to sort a wxListCtrl by column? Or am I approaching this the wrong way?
el-512
In need of some credit
In need of some credit
Posts: 9
Joined: Wed May 04, 2005 7:00 am

Post by el-512 »

Hi,

I had a similar problem last monday.

I am not sure, if this is, what you are looking for! As my List Conrol is not a derived class.

Here my solution, but to be honest it's a kind of .... because I needed two values, but you can only pass one INT. So you either pass a pointer of a struct (typecasted to an int) or you use one public variable - what I did.

In your case, the solution should be a bit easier, as you should have have the class-pointer.

The sort itself could be a bit easier, but I implemented a "sort of numbers", so if both values are numbers, a "3" is smaller than "15".

Here my code:

Code: Select all

    EVT_LIST_COL_CLICK      (-1,            MainFrame::list_sort)

wxListCtrl * item_list = NULL;      // must be public !! 
                                               
void MainFrame::list_sort( wxListEvent& event ){
int column;

    column = event.GetColumn();
    if (column > 4) {         // my list has only 5 colums!!
        return;
    }
    item_list->SortItems(MyListCompareFunction,column);
}


int wxCALLBACK MyListCompareFunction(long item1, long item2, long sortData)
{

wxListItem listItem1, listItem2;
long itemId1;
long itemId2;
int cur_col;
wxString str1, str2;

    if (item_list == NULL) return 0;

    itemId1 = Item_list->FindItem(-1, item1);
    itemId2 = Item_list->FindItem(-1, item2);
    listItem1.SetMask(wxLIST_MASK_TEXT);
    listItem2.SetMask(wxLIST_MASK_TEXT);
    listItem1.SetId(itemId1);
    listItem2.SetId(itemId2);

    cur_col  = (int)(sortData);
    next_col = sortData +1;
    if (next_col > 4) { next_col = 0; }

    listItem1.SetColumn(cur_col);
    Item_list->GetItem(listItem1);

    listItem2.SetColumn(cur_col);
    Item_list->GetItem(listItem2);

    str1 = listItem1.GetText();
    str2 = listItem2.GetText();

    if (str1.IsNumber() && str2.IsNumber()) {
        str1.ToLong(&itemId1);
        str2.ToLong(&itemId2);
        if (itemId1 > itemId2) return  1;
        if (itemId1 < itemId2) return -1;
        return 0;
    }

    if (str1 > str2) return  1;
    if (str1 < str2) return -1;
    return 0;
}

Luke727
Earned some good credits
Earned some good credits
Posts: 130
Joined: Wed Dec 22, 2004 9:31 am

Post by Luke727 »

I actually found a decent way to do it on some MFC message board. I've got a long that is incremented every time I insert an item. It is used as the client data, so every item has a unique identifier. The relevant code:

Code: Select all

class MyListCtrl;

typedef struct
{
   MyListCtrl *ListCtrl; // the list control to sort
   int Column; // the column in the list control to sort
   bool SortAscending; // the order in which to sort the items
} MySortInfo;

class MyListCtrl : public wxListCtrl
{
   public:
      ...
      wxString GetTextByColumn(long index, int column); // get the text at the specified index,column location
      MySortInfo SortInfo; // used for sorting items
   private:
      void OnColumnClick(wxListEvent &); // user clicked a column; will sort items based on that column
      DECLARE_EVENT_TABLE()
};

BEGIN_EVENT_TABLE(MyListCtrl, wxListCtrl)
   EVT_LIST_COL_CLICK(wxID_ANY, MyListCtrl::OnColumnClick)
END_EVENT_TABLE()

wxString MyListCtrl::GetTextByColumn(long index, int col)
{
   wxListItem Item; // the item whose text we want
   Item.SetId(index); // set the index
   Item.SetColumn(column); // set the column
   Item.SetMask(wxLIST_MASK_TEXT); // enable GetText()
   GetItem(Item); // get the item
   return Item.GetText(); // get and return its text
}

void MyListCtrl::OnColumnClick(wxListEvent &event)
{
   if(event.GetColumn() == SortInfo.Column) // user clicked same column as last time, change the sorting order
   {
      SortInfo.SortAscending = SortInfo.SortAscending ? FALSE : TRUE;
   }
   else // user clicked new column, sort ascending
   {
      SortInfo.SortAscending = TRUE;
   }
   SortInfo.Column = event.GetColumn(); // set the column
   SortInfo.ListCtrl = this; // set the list control pointer
   SortItems(MyListCompareFunction, (long) &SortInfo); // sort the items, passing a pointer to the SortInfo object
}

int wxCALLBACK MyListCompareFunction(long item1, long item2, long sortinfo) // the callback sorting function, takes a pointer to a SortInfo object
{
   MySortInfo *SortInfo = (MySortInfo *) sortinfo;
   int Column = SortInfo->Column; // get the column
   MyListCtrl *ListCtrl = SortInfo->ListCtrl; // get the list control
   bool SortAscending = SortInfo->SortAscending; // get the sorting order
   long index1 = ListCtrl->FindItem(-1, item1); // get the index of the first item
   long index2 = ListCtrl->FindItem(-1, item2); // get the index of the second item
   wxString string1 = ListCtrl->GetTextByColumn(index1, Column); // get the text of the first item
   wxString string2 = ListCtrl->GetTextByColumn(index2, Column); // get the text of the second item
   if(string1.Cmp(string2) < 0)
   {
      return SortAscending ? -1 : 1;
   }
   else
   if(string1.Cmp(string2) > 0)
   {
      return SortAscending ? 1 : -1;
   }
   else
   {
      return 0;
   }
}
If you want to get really fancy, have a look at http://www.codeguru.com/Cpp/controls/li ... php/c4179/. It's Win32/MFC, but it translates pretty well into wxWidgets API.
el-512
In need of some credit
In need of some credit
Posts: 9
Joined: Wed May 04, 2005 7:00 am

Post by el-512 »

Hi,
I have to admit, your code looks better. I think, I will change my code.

But for me the mayor problem were these lines:

itemId1 = Item_list->FindItem(-1, item1);
listItem1.SetMask(wxLIST_MASK_TEXT);
listItem1.SetId(itemId1);
listItem1.SetColumn(cur_col);
Item_list->GetItem(listItem1);

But as you derived a list class, this is easier for you.

Good luck
Post Reply