Laza
Laza

Reputation: 33

Strange behaviour while sorting CMFCListCtrl

I have extended CMFCListCtrl class mainly to be able to easily sort by columns. From what I learned, it should be enough to implement Sort and OnCompareItems functions.

What happens is that sort works fine with 1st column which type is integer but it gives strange ordering for 2nd column, which type is string. There are no errors, ordering is changed but not alphabetically.


int MyCMFCListCtrl::OnCompareItems(LPARAM lParam1, LPARAM lParam2, int iColumn)
{
    if (iColumn == 0)
    {
        if (lParam1 > lParam2)
            return -1;
        else if (lParam1 < lParam2)
            return 1;
        else
            return 0;
    }
    else if (iColumn == 1)
    {
        CString strCol1, strCol2;
        strCol1 = GetItemText(lParam1, iColumn);
        strCol2 = GetItemText(lParam2, iColumn);
        return strCol1.Compare(strCol2);
    }
    return 0;
}

void MyCMFCListCtrl::Sort(int iColumn, BOOL bAscending, BOOL bAdd)
{
    // Sort available for first and second column
    if (iColumn > 1) return;
    CMFCListCtrl::Sort(iColumn, bAscending, bAdd);
}

Values I get in OnCompareItems are ok for both columns. But the end result is valid only for the first one. Are there any other functions necessary for this to work?

Upvotes: 3

Views: 873

Answers (1)

Barmak Shemirani
Barmak Shemirani

Reputation: 31609

CMFCListCtrl::Sort will call CListCtrl::SortItems. SortItems will pass LPARAM item data to the callback function. This LPARAM is a value which can be set with SetItemData.

That means the lParam1 and lParam2 in OnCompareItems, refere to LPARAM data only.

GetItemText(lParam1, iColumn) is undefined behavior in this case because lParam1 does not refer to row number.

See also documentation for LVM_SORTITEMS and LVM_SORTITEMSEX


You can override Sort and call SortItemsEx instead. This way lParam1 and lParam2 will refer to row numbers, and GetItemText(lParam1, iColumn) will be a valid call, as shown below. Note that (iColumn == 0) condition is used only if SetItemData was called earlier.

int MyCMFCListCtrl::OnCompareItems(LPARAM lParam1, LPARAM lParam2, int iColumn)
{
    if(iColumn == 0)//assuming SetItemData was called earlier
        return lParam1 - lParam2;

    CString strCol1 = GetItemText(lParam1, iColumn);
    CString strCol2 = GetItemText(lParam2, iColumn);
    return strCol1.Compare(strCol2);
}

void MyCMFCListCtrl::Sort(int iColumn, BOOL bAscending, BOOL bAdd)
{
    if(iColumn == 0)//assuming SetItemData was called earlier
    {
        //call SortItem and get LPARAM data in call back function
        CMFCListCtrl::Sort(iColumn, bAscending, bAdd);
        return;
    }

    //call SortItemEx instead, get row numbers in callback function
    CWaitCursor wait;
    GetHeaderCtrl().SetSortColumn(iColumn, bAscending, bAdd);
    m_iSortedColumn = iColumn;
    m_bAscending = bAscending;
    SortItemsEx(CompareProc, (LPARAM)this);
}

Upvotes: 4

Related Questions