CListCtrl
Sorting by any column
Site Map Feedback
Up Properties Select Sort

It's Easy to Add Functionality to Windows Controls!

Well, there are plenty of solutions to this and none of them are good!
Most use lots of code and lots of memory.
You can either write your own QuickSort function and swap Rows your self by swapping every item in every column...
...or you can use the CListCtrl's existing SortItems(...) function - but that only provides you with a pointer to the ItemData of the Rows it wants you to compare...
Most solutions involve duplicating all the data in the ListCtrl in structures pointed to by ItemData.
So one solution duplicates code that's already been written, and one duplicates all the data in the control!
To minimise the amount of code (and, therefore, the potential for bugs), don't use the DIY method (and hope that the SortItems(...) function swaps rows with a simple pointer swap, so it should be far faster).
So the best that can be done is leave the ItemData alone until a Sort is required, then replace it with a pointer to a structure that holds the old ItemData value, and the value from the Column which is to be sorted.
After the Sort the Item Data can be restored to its original state and all the memory that was used for duplication can be freed.
The type of the column to sort will usually be the same for the whole column, so its up to the class to work out what type of sorting to use (String/Icon/Date/Number).
The original List had one Date column, so it was used the ItemData to store the CTime::GetDate() value so that date comparisons could be fast.
Since the data to sort by for that Date column is already in the ItemData, the standard SortItems call can be used:
SortItems(CompareDates, 0);
where CompareDates is:
  static int CALLBACK CompareDates(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) {return (lParamSort ? lParam1-lParam2 : lParam2-lParam1);}
The lParamSort parameter is used to indicate whether the sort should be ascending or descending.
But for all the other columns the data to sort but first be put into ItemData without loosing ItemData.
Use Class-Wizard to get an OnColumnclick handler for your CListCtrl-derived class, and copy and paste the code in Sort.h:
Of course, your list won't have the same columns as mine did, so you'll still have some coding to do before you see anything work, but hopefully its all easy to understand!
class ... : public CListCtrl {
  ...
  struct ItemData {
    ItemData(DWORD dwData, CString Text) : dwData(dwData), Text(Text) {}
    ItemData(DWORD dwData, int Icon    ) : dwData(dwData), Icon(Icon) {}
    DWORD dwData;
    CString Text;
    int Icon;
  };

  struct SortStruct { // this just keeps all the sorting data together so you don't have to initialise it in the main constructor.
    int  SortCol;
    bool Ascending;
    SortStruct() : SortCol(6), Ascending(true) {}
    void SetCol(int Col) {Ascending=(SortCol==Col ? !Ascending : true); SortCol=Col;}
  } SortData;

  int GetItemImage(int Row, int Col) {
    LV_ITEM lvi;
    lvi.mask=LVIF_IMAGE;
    lvi.iItem=Row;
    lvi.iSubItem=Col;
    GetItem(&lvi);
    return lvi.iImage;
  }

  void OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult) {
    *pResult=0; // Not used
    int Row=GetItemCount();
    int Col=((NM_LISTVIEW*)pNMHDR)->iSubItem;
    SortData.SetCol(Col);
    switch(Col) {
      case 0: // Priority
      case 1: // Mark
      case 2: // Flag
      case 3: // Attachments
        while(Row--) SetItemData(Row, (DWORD)(new ItemData(GetItemData(Row), GetItemImage(Row, Col))));
        break;
      case 4: // To/From
      case 5: // Subject
      case 7: // File Name
        while(Row--) SetItemData(Row, (DWORD)(new ItemData(GetItemData(Row), GetItemText(Row, Col))));
        break;
      case 6: // Date CString
        SortItems(CompareDates, SortData.Ascending);
      default: return;
    }
    SortItems(CompareFunction, (DWORD)this);
    Row=GetItemCount();
    while(Row--) {
      ItemData* pid=(ItemData*)GetItemData(Row);
      if(pid) SetItemData(Row, pid->dwData);
      delete pid;
  } }

  int CALLBACK CompareFunction(LPARAM lParam1, LPARAM lParam2, LPARAM lParamData) {
    if(!lParam1 || !lParam2) return 0;
    CMailBox* Me=(CMailBox*)lParamData;
    ItemData* pid1=(ItemData*)(Me->SortData.Ascending ? lParam1 : lParam2);
    ItemData* pid2=(ItemData*)(Me->SortData.Ascending ? lParam2 : lParam1);
    int Col=Me->SortData.SortCol;
    switch(Col) {
      case 0: // Priority
      case 1: // Mark
      case 2: // Flag
      case 3: // Attachments
        return pid2->Icon - pid1->Icon;
      case 4: // To/From
      case 5: // Subject
      case 7: // File Name
        return pid1->Text.CompareNoCase(pid2->Text);
//    case 6: // Date CString               Not needed: a quicker method is used for this column.
//      return pid2->dwData-pid1->dwData;
      default: return 0;
  } }
  ...
};

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


 (6289) Last modified: Fri, 15 May 2009 14:27:32 +0000