Click to See Complete Forum and Search --> : how to sort owner-drawn list control


junpin Li
April 13th, 1999, 09:19 PM
I want to use a column to sort a own-draw list control. Could somebody give me some clues? Thanks.

LALeonard
April 14th, 1999, 12:59 PM
Do this in your CListCtrl-derived class.



BEGIN_MESSAGE_MAP(MyListCtrl, CListCtrl)
//{{AFX_MSG_MAP(MyListCtrl)
ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, OnColumnclick)
END_MESSAGE_MAP()



// This is a reflected message handler, which means that this control
// handles this message instead of the parent window. Any handler
// for this message in the parent window will override this handler
// (unless it calls this handler first, of course).
void MyListCtrl::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*) pNMHDR;

SortColumn(pNMListView->iSubItem);
*pResult = 0;
}

// Split this code out so that it can be called from outside this class.
void MyListCtrl::SortColumn(int nColumn, bool bToggle /* = true */ )
{
// If a different column is being sorted from the last time we
// sorted, we always start off ascending.
if (nColumn != m_nColumnSort) {
m_bSortAscending = ! TRUE;
}
else if (bToggle) {
m_bSortAscending = ! m_bSortAscending;
}

m_nColumnSort = nColumn;
_ASSERTE(IsColumn(m_nColumnSort));

// Now, the only way the CListCtrl can know how to sort is by each
// item's LPARAM, so let's see if the first item has one. If so,
// we assume they all do, and use them; otherwise, we use the item number!
BOOL bHasLparams(IsItem(0) && GetItemData(0));

if (! bHasLparams) {

for (int nItem = 0; IsItem(nItem); nItem++) {
VERIFY(SetItemData(nItem, nItem));
}
}

// Call the sort routine the first time. (Make sure we do an alphabetic sort.)
m_bNumericSort = false;
VERIFY(SortItems(CompareFunc, reinterpret_cast <DWORD> (this)));

// If the first and last item contain only numeric strings, it's a
// safe bet that this is a numeric column.
if (0 < GetItemCount()) {
CString sChars("$.%, 0123456789");
CString sFirst(GetItemTextWorks(0, m_nColumnSort));
CString sLast(GetItemTextWorks(GetItemCount() - 1, m_nColumnSort));
m_bNumericSort = (NULL == _strspnp(sFirst, sChars) && NULL == _strspnp(sLast, sChars));
//TRACE1("Setting numeric sort flag to %s\n", m_bNumericSort ? "true" : "false");

// Call the sort routine the second time for a numeric sort.
if (m_bNumericSort) {
VERIFY(SortItems(CompareFunc, reinterpret_cast <DWORD> (this)));
}
}

// I set the lParams back to zero so that stale data isn't hanging around
// (because the sort has changed the item numbers).
if (! bHasLparams) {

for (int nItem = 0; IsItem(nItem); nItem++) {
VERIFY(SetItemData(nItem, 0));
}
}
}

// This is the function that the base CListCtrl code calls whenever it
// needs to compare two items.
/* static */ int CALLBACK MyListCtrl::CompareFunc(LPARAM lParam1, LPARAM lParam2,
LPARAM lParamSort)
{
// Retrieve the "this" we packaged in OnColumnclick().
MyListCtrl* pListCtrl = reinterpret_cast <MyListCtrl*> (lParamSort);
ASSERT_VALID(pListCtrl);

// Retrieve the two items to compare.
LV_FINDINFO lvi;
::ZeroMemory(&lvi, sizeof(lvi));
lvi.flags = LVFI_PARAM;

lvi.lParam = lParam1;
int nItem1(pListCtrl->FindItem(&lvi));
_ASSERTE(-1 != nItem1);

lvi.lParam = lParam2;
int nItem2(pListCtrl->FindItem(&lvi));
_ASSERTE(-1 != nItem2);

// Now get the two strings to compare.
CString s1(pListCtrl->GetItemTextWorks(nItem1, pListCtrl->m_nColumnSort));
CString s2(pListCtrl->GetItemTextWorks(nItem2, pListCtrl->m_nColumnSort));

// Now compare them, remembering to take into account which direction
// we are sorting in at the moment. "The comparison function must return
// a negative value if the first item should precede the second, a
// positive value if the first item should follow the second, or zero if
// the two items are equivalent."
int nReturn(0);

// Numeric sort.
if (pListCtrl->m_bNumericSort) {
nReturn = atoi(s1) - atoi(s2);

if (! pListCtrl->m_bSortAscending) {
nReturn = -nReturn;
}
}
else {
// Alphabetic sort.
nReturn = s1.CompareNoCase(s2);

if (! pListCtrl->m_bSortAscending) {
nReturn = -nReturn;
}
}

return nReturn;
}


<HR>LA Leonard - <A HREF="http://www.DefinitiveSolutions.com/">Definitive Solutions, Inc.</A>

junpin Li
April 15th, 1999, 07:58 PM
Thanks for help. I tried the codes but not very successful. There are two functions missing: IsItem(), IsColumn(). I programmed IsItem by checking if it is in the range (nItem < GetItemCount()). IsColumn() by returning GetColumn(nCol, &lv_column). The _strspnp() is undeclared though I included tchar.h (I forced false numericsorting). After these, I got sorted results only when I clicked column three times (first time, nothing, 2nd, a little bit sorting, 3rd, works fine) Can you tell me why? and how you i,plement IsColumn, and IsItem? Thanks a lot.

LALeonard
April 16th, 1999, 08:36 AM
Oops, sorry... here they are:

// Does the sent item exist?
BOOL MyListCtrl::IsItem(int nItem) const
{
_ASSERTE(0 <= nItem);

return -1 < nItem && GetItemCount() > nItem;
}

// Does the sent column exist?
BOOL MyListCtrl::IsColumn(int nCol) const
{
_ASSERTE(0 <= nCol);

LV_COLUMN lvc;
::ZeroMemory(&lvc, sizeof(lvc));
lvc.mask = LVCF_WIDTH;

return -1 < nCol && GetColumn(nCol, &lvc);
}

HTH.


<HR>LA Leonard - <A HREF="http://www.DefinitiveSolutions.com/">Definitive Solutions, Inc.</A>

junpin Li
April 16th, 1999, 09:41 PM
Thanks again. I implemented IsItem, IsColumn almost the same you did. But you still didn't anwser the following questions:
1) _strspnp compile error (though I include tchar.h)
2) I didn't get sorted results until I click the column head the third time. After tha it works fine on any column.
I appreciate if you can explain that.