In order to sort the items in a ListView
control, there must be an LVITEM structure associated with the item. MFC
provides this for the developer, allowing an item to be inserted with a simple
InsertItem(int nItem, LPCTSTR lpszItem) function call that creates the
structure with reasonable defaults. Such a buffer from the underlying complexity
can sometimes be misleading. However, the LVITEM structure is an
important key to manipulating ListView items, including the sorting
mechanism.
The lParam element of LVITEM provides necessary
information. When the SortItems function of the CListCtrl class is
called, it must provide a function pointer to a sort callback function, and an
application-defined DWORD value. During the sort, the callback function
is repeatedly invoked as two items from the list control are selected for
comparison. The parameters it receives are the lParam element from each
item's LVITEM structure, and the DWORD value passed by the
SortItems call.
The code below represents a simple example of
sorting a list of ten U.S. Presidents in a ListView control. The presidents are
initially stored in a static multi-dimensional CString
array.
static CString strData[10][3] =
{
{ _T("Washington"), _T("George"), _T("1789-1797") },
{ _T("Adams"), _T("John"), _T("1797-1801") },
{ _T("Jefferson"), _T("Thomas"), _T("1801-1809") },
{ _T("Madison"), _T("James"), _T("1809-1817") },
{ _T("Monroe"), _T("James"), _T("1817-1825") },
{ _T("Adams"), _T("John Quincy"), _T("1825-1829") },
{ _T("Jackson"), _T("Andrew"), _T("1829-1837") },
{ _T("Van Buren"), _T("Martin"), _T("1837-1841") },
{ _T("Harrison"), _T("William Henry"), _T("1841") },
{ _T("Tyler"), _T("John"), _T("1841-1845") }
};
The callback sort function may be
defined statically as a member of a class or, as here, simply as a global
function:
int CALLBACK SortFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);
The lParam element can be anything from
simple to highly complex. Frequently, a structure is useful in this context,
allowing multiple pieces of data to be referenced. For this example, a structure
called ITEMDATA was defined to hold the three elements comprising a given
item:
typedef struct {
LPTSTR pszLastName;
LPTSTR pszFirstName;
LPTSTR pszTerm;
} ITEMDATA, *PITEMDATA;
In this example, the structure was
defined in a CDialog class's header file, and a member variable of a
pointer to an array of 10 was defined:
ITEMDATA* m_pData[10];
A ListView control was added to a
dialog, and a member variable defined called m_ctlListView. The items were added
in OnInitDialog:
m_ctlListView.InsertColumn(0, _T("Last Name"), LVCFMT_LEFT, 100);
m_ctlListView.InsertColumn(1, _T("First Name"), LVCFMT_LEFT, 100);
m_ctlListView.InsertColumn(2, _T("Term"), LVCFMT_LEFT, 100);
for (int i=0; i<10; i++)
{
m_pData[i] = new ITEMDATA;
m_pData[i]->pszLastName = (LPTSTR)(LPCTSTR)strData[i][0];
m_pData[i]->pszFirstName = (LPTSTR)(LPCTSTR)strData[i][1];
m_pData[i]->pszTerm = (LPTSTR)(LPCTSTR)strData[i][2];
m_ctlListView.InsertItem(i, strData[i][0]);
m_ctlListView.SetItemText(i, 1, strData[i][1]);
m_ctlListView.SetItemText(i, 2, strData[i][2]);
m_ctlListView.SetItemData(i, (LPARAM)m_pData[i]);
}
Three columns were inserted for the last
name, first name, and term of office. Then, for each of the ten items, a new
ITEMDATA structure is allocated and initialized from the CString array. The item
is inserted very simply, using only the index and the last name string, then the
text is set for the other two columns of the item. Finally, the function
SetItemData is called, passing the new ITEMDATA as a parameter. This
reinitializes the lParam of the item's LVITEM structure, and prepares the way
for the sort.
MFC in Visual C++ 6.0 has a problem with header
notifications for the ListView control. Although a handler can be added, in the
current version it isn't called. For instance, use Class Wizard or the WizardBar
to add a Windows Message Handler. If the ID for the ListView control is
highlighted, a number of notification messages are available for selection. To
sort the items when the header is clicked for a given column, select the
notification HDN_ITEMCLICK. An ON_NOTIFY message map entry is
generated, as well as a handler function. For the current example, the entry
appears as follows:
ON_NOTIFY(HDN_ITEMCLICK, IDC_LIST1, OnItemclickList1)
The problem here is that the
notification doesn't actually originate from the ListView control; instead, the
Header control created by the ListView sends the notification. The message map
entry listed above does not work. The fix is simple, however, since the Header
control always has an ID of 0, the macro can be edited to work
correctly:
ON_NOTIFY(HDN_ITEMCLICK, 0, OnItemclickList1)
Then, in the OnItemclickList1 handler,
the SortItems call is made:
void CSortListDlg::OnItemclickList1(NMHDR* pNMHDR, LRESULT* pResult)
{
NMLISTVIEW *pLV = (NMLISTVIEW *) pNMHDR;
m_ctlListView.SortItems(SortFunc, pLV->iItem);
*pResult = 0;
}
The notification message header
(NMHDR) is actually a ListView notification, NMLISTVIEW, that
contains the index to the column that was clicked. In this example, this is
represented by iItem. More complex lists might need to reference the
iSubItem element of this structure as well. The address of the callback
function is passed to SortItems, along with the column number which was
clicked.
The SortFunc routine is called repeatedly as pairs of the
ListView's items are passed to the function for comparison. The first two
parameters are the lParam element of the respective items' LVITEM
structure, and the third parameter (application-defined) is the column number
provided in the SortItems call.
int CALLBACK SortFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
int nRetVal;
PITEMDATA pData1 = (PITEMDATA)lParam1;
PITEMDATA pData2 = (PITEMDATA)lParam2;
switch(lParamSort)
{
case 0: // Last Name
nRetVal = strcmp(pData1->pszLastName,
pData2->pszLastName);
break;
case 1: // First Name
nRetVal = strcmp(pData1->pszFirstName,
pData2->pszFirstName);
break;
case 2: // Term
nRetVal = strcmp(pData1->pszTerm, pData2->pszTerm);
break;
default:
break;
}
return nRetVal;
}
The column index passed in
lParamSort determines which element of the ITEMDATA objects passed in
lParam1 and lParam2 should be used for comparison. The result is
returned and the process continues until all items have been sorted.
As a
reminder, the ITEMDATA structures which were allocated for the list items need
to eventually be destroyed. For this example, the WM_DESTROY handler for
the dialog iterates through the member elements and deletes
them.
for (int i=0; i<10; i++)
delete m_pData[i];