typedef struct { // data layout for each row
char* szName;
char* szDate;
char* szRef;
char* szType;
char* szStatus;
char* szComment;
} SampleListData;
//------------------------ the data used in these examples
SampleListData arData[]= {
{"Smith, Joe", "2009-12-14", "A22222", "Std", "Sent", "This is a comment" },
{"Smith, Alex", "2009-12-15", "A12346", "Std", "Sent", "This is a comment" },
{"Jones, Jane", "2009-12-13", "A22223", "Jumbo", "Ready", "This is a comment" },
{"Albert, Q.", "2009-12-12", "A99999", "SP", "Sent", "This is a comment" },
{0, 0, 0, 0, 0, 0 }, // signal end of list
};
And here's the loop that shows how these data items are put into the list:
//--------- uses the CListCtrlEx.PutItem() function from previous article
void CListClmsDlg::PopulateList() {
SampleListData* pr= arData;
int nListIdx= 0;
while( pr->szName ) {
m_ctlList.PutItem( nListIdx, 0, pr->szName , TRUE);
m_ctlList.PutItem( nListIdx, 1, pr->szDate );
m_ctlList.PutItem( nListIdx, 2, pr->szRef );
m_ctlList.PutItem( nListIdx, 3, pr->szType );
m_ctlList.PutItem( nListIdx, 4, pr->szStatus );
m_ctlList.PutItem( nListIdx, 5, pr->szComment );
m_ctlList.SetItemData( nListIdx, (DWORD)pr ); // << NEEDED
nListIdx++;
pr++;
}
}
An important part of this is that at
line 13, after inserting a new row and populating the subitems, the function also saves a pointer to the original data (a SampleListData* value) as the
item data. This is a key to providing the ability to sort the list on the fly.
int CALLBACK MyCompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
SampleListData* pr1= (SampleListData*) lParam1;
SampleListData* pr2= (SampleListData*) lParam2;
CString s1= pr1->szName;
CString s2= pr2->szName;
int nRet= 0; // indicates they are equal
if ( s1 > s2 ) nRet= 1; // item 1 should come after item 2
if ( s1 < s2 ) nRet= -1; // item 1 should come before item 2
return( nRet );
}
void CListClmsDlg::OnLvnColumnclickList1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
m_ctlList.SortItems( MyCompareFunc, 0 );
*pResult = 0;
}
This requires some explanation. When you invoke the CListCtrl.SortItems() function, you pass to it the address of a comparison function. That comparison function will operate on the
item data that is associated with two different rows in the list control. That's why we used...
m_ctlList.SetItemData( nListIdx, (DWORD)pr ); // << NEEDED for sorting
...in the PopulateList() function.
typedef enum {
SORT_None = 0,
SORT_AZ = 1,
SORT_ZA = -1,
} SortOrder;
SortOrder geOrder= SORT_None; // global variable, for now
void CListClmsDlg::OnLvnColumnclickList1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
if ( geOrder == SORT_None ) geOrder= SORT_AZ;
else geOrder= (SortOrder)-geOrder; // -1 to 1, 1 to -1
m_ctlList.SortItems( MyCompareFunc, 0 );
*pResult = 0;
}
Notice that the SortOrder enumerated data type is set up so that it can toggle by reversing the sign (-1 becomes 1 and
vice versa). The callback function now looks like this:
int CALLBACK MyCompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
SampleListData* pr1= (SampleListData*) lParam1;
SampleListData* pr2= (SampleListData*) lParam2;
if ( geOrder == SORT_ZA ) { // sort descending, just swap
pr2= (SampleListData*) lParam1;
pr1= (SampleListData*) lParam2;
}
CString s1= pr1->szName;
CString s2= pr2->szName;
int nRet= 0;
if ( s1 > s2 ) nRet= 1;
if ( s1 < s2 ) nRet= -1;
return( nRet );
}
I've found that the best way to reverse the sort order is to just swap the parameters right at the start (
lines 6-9). It's easier than an
if...then for each case (which can get confusing... "Am I going up or down? If down, do I use
< or
> ?" etc.)
SortOrder geOrder= SORT_None;
int gnSortClm= -1; // not set
void CListClmsDlg::OnLvnColumnclickList1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
if ( geOrder== SORT_None) geOrder= SORT_AZ;
else geOrder = (SortOrder) -geOrder;
gnSortClm= pNMLV->iSubItem; // <<<<< save column# for the callback
m_ctlList.SortItems( MyCompareFunc, 0 );
*pResult = 0;
}
...and here's the callback:
int CALLBACK MyCompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
SampleListData* pr1= (SampleListData*) lParam1;
SampleListData* pr2= (SampleListData*) lParam2;
if ( geOrder == SORT_ZA ) { // sort descending, just swap
pr2= (SampleListData*) lParam1;
pr1= (SampleListData*) lParam2;
}
CString s1, s2;
switch( gnSortClm ) {
default: s1= pr1->szName; s2=pr2->szName; break; // clm 0
case 1: s1=pr1->szDate; s2=pr2->szDate; break;
case 2: s1=pr1->szRef; s2=pr2->szRef; break;
case 3: s1=pr1->szType; s2=pr2->szType; break;
case 4: s1=pr1->szStatus; s2=pr2->szStatus; break;
case 5: s1=pr1->szComment; s2=pr2->szComment; break;
}
int nRet= 0;
if ( s1 > s2 ) nRet= 1;
if ( s1 < s2 ) nRet= -1;
return( nRet );
}
In case you are wondering...
What if the user has rearranged the columns, as described in the
previous article? Well, the answer is... Yes, it works perfectly. The LPNMLISTVIEW that comes into the OnColumnClick handler provides the
original column index -- even if the user has shuffled them and even if you have restored the arrangement from a previous session.
Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.
Comments (0)