CSortedListCtrl reusable base class

Download the source and example.

I found several articles about sorting a CListCtrl, adding visual feedback in the header... on the codeguru page and I have encapsulated this into a reusable base class CSortedListCtrl that encapsulates code from several other articles. With this class, the user doesn't have to care about all this sorting stuff, header control, sorting callback... He just has to override some virtual routines to implement its sorting algorithm. The Zip file contains a Word 97 DOC for the documentation (ed. which is duplicated here), the SortedListCtrl project encapsulating all the code from previous articles and a project (TestListCtrl) showing how to use the CSortedListCtrl class. It has been compiled with Visual Studio 97. I hope this will be useful to other programmers just like the articles on the CodeGuru page were for me.


Introduction

This is a reusable CSortedListCtrl class based on several articles from the ListView Control section of CodeGuru :

  • "Indicating sort order in header control - Zafir Anjum"
  • "Sorting the list when user clicks on column header - Zafir Anjum"
  • "Vertical lines for column borders - Zafir Anjum"

How to use the class

A compilable test project is available and I will only use some extracts here to explain the code. There is no explanation on the code from the other articles, just go there and read them if you are interested.

I have put all the CSortedListCtrl stuff in a separate library file so I can use it from all my projects. To use the class, you need the "SortedListCtrl" project which will produce a library. Insert this project into your project (in the sample, I have used a Dialog based app called TestListCtrl) that will be using the CSortedListCtrl and set a dependency so it will recompile automatically.

CSortedListCtrl acts as a base class like CDialog. So you will need to derive your own class : let's say CMyListCtrl. You can make it using Class Wizard : you drop a CListCtrl on your dialog box, set it in report mode and add a class CMyListCtrl setting CListCtrl as a base class but you will have to change CListCtrl into CSortedListCtrl in Wizard generated code (in .h and message map) You will also need to #include "SortedListCtrl.h", add an additional include directory (the SortedListCtrl directory) in the project settings, define a variable for your list control (m_ListCtrl in the example) and set its type to CMyListCtrl (not CListCtrl).

I use a ListCtrl with two columns and no image. Let's say a column Name of type string and a column Number of type integer to illustrate the sorting routine with two different types. You need to build the list columns in the OnInitDialog routine. To clearly show the code added, this is done in an ExtraInit function called from OnInitDialog :


void CTestListCtrlDlg::ExtraInit()
{
	CRect Rect1;
	m_ListCtrl.GetWindowRect(Rect1);
	int cx = (Rect1.Width() - 4) >> 1;

	m_ListCtrl.InsertColumn(0, "Name", LVCFMT_LEFT, cx);
	m_ListCtrl.InsertColumn(1, "Number", LVCFMT_LEFT, cx);

	FillList();

	// Add full row selection to listctrl
	m_ListCtrl.SetFullRowSel(TRUE);

	// Sort the list according to the Name column
            m_ListCtrl.SortColumn(0, TRUE);
}

SetFullRowSel is a method of CSortedListCtrl that allows to select the entire line, not only the first column while the SortColumn method allows you to sort the list on a given column and choosing ascending or descending order (sorting mechanism is explained later).

Now let's have a look at the FillList routine :


void CTestListCtrlDlg::FillList()
{
    CMyItemInfo *pItemInfo;
     int iItem = 0;
    CString Name;
    int Number;

    Name = "Sharon Stone";
    Number = -1;

    m_ListCtrl.InsertItem(iItem, LPSTR_TEXTCALLBACK);    
    pItemInfo = new CMyItemInfo (iItem, Name, Number);
    m_ListCtrl.SetItemData(iItem, (DWORD)pItemInfo);

    m_ListCtrl.SetItemText(iItem, 1, LPSTR_TEXTCALLBACK);

    iItem++;	

    Name = "Julia Roberts";
    Number = 1;

    m_ListCtrl.InsertItem(iItem, LPSTR_TEXTCALLBACK);    
    pItemInfo = new CMyItemInfo (iItem, Name, Number);
    m_ListCtrl.SetItemData(iItem, (DWORD)pItemInfo);

    m_ListCtrl.SetItemText(iItem, 1, LPSTR_TEXTCALLBACK);
}

It just fills the list with two items but won't compile yet because it needs another class used by the sorting mechanism. Sorting the list using the standard mechanism provided by CListCtrl requires that you attach some item data to your items because what will be given to you in the compare routine (see later) will be pointers to these item data. In order to hide the callback mechanism used by CListCtrl and be able to use polymorphism, the data attached to the item is encapsulated in a class. I give you a base class CitemInfo from which you can derive your own class, let's say CMyItemInfo. You can do this with class wizard, specifying a generic class deriving publicly from CItemInfo (ItemInfo.h is provided in the SortedListCtrl project). You will have to do some #includes with ItemInfo.h and MyItemInfo.h. Now, you can just add some class members, constructor and methods to your MyItemInfo class to set and retrieve easily information. To make it easy, I have put everything in the MyItemInfo.h file :


#include "ItemInfo.h"

class CMyItemInfo : public CItemInfo  
{
public:

    CMyItemInfo(int iItem, CString& Name, int Number) :
		CItemInfo(iItem),
		m_Name(Name),
		m_Number(Number) {m_NumberAsString.Format("%d",Number); };

    virtual ~CMyItemInfo() {};

    CString& GetName() {return m_Name;}
    int GetNumber() {return m_Number;}
    CString& GetNumberAsString() {return m_NumberAsString;}

private:

    CMyItemInfo();

    CString m_Name;
    CString m_NumberAsString;
    int m_Number;

};


The basic idea for the sort to work is to put the data inside the MyItemInfo class which will be passed to the comparison routine. In order not to duplicate data (it's a time/space compromise choice here), the LPSTR_TEXTCALLBACK is used. To make it work, you must add the following line in MyListCtrl.h :


afx_msg void OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult);

and the following one in the message map of MyListCtrl.cpp :


ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetDispInfo)

Then, you have to implement the OnGetDispInfo routine :


void CMyListCtrl::OnGetDispInfo(NMHDR* pNMHDR, LRESULT* pResult) 
{
	LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;

	if (pDispInfo->item.mask & LVIF_TEXT) {

		CMyItemInfo* pAppItem = reinterpret_cast(pDispInfo->item.lParam);

		switch (pDispInfo->item.iSubItem) {

		case 0 :
			lstrcpy (pDispInfo->item.pszText, pAppItem->GetName());
			break;
		case 1 :
			lstrcpy (pDispInfo->item.pszText, pAppItem->GetNumberAsString());
			break;
		}
	}
	*pResult = 0;
}

It's not finished yet ! You have to give a correct constructor for the CMyListCtrl class :


CMyListCtrl::CMyListCtrl() : CSortedListCtrl(TRUE, TRUE)
{
}

The first constructor parameter tells the base class if you want to sort the column (it's normally what you want here !) and the second parameter informs the base class to handle the deletion of the item data itself. In fact, if you remember the code in FillList, we allocate objects of type CMyItemInfo on the heap to attach to the list items. When elements of the list are deleted, these MyItemInfo must be also deleted and the base class can do it for you. This is possible because CMyItemInfo derives from ItemInfo and the CItemInfo destructor is virtual.

Now, we must still provide a comparison routine : this is done by overridding a virtual function from CSortedListCtrl that is used to sort the elements in the list. This is just an OO translation of the callback mechanism used by the standard control that's now hidden in the base class.

Just add the following method declaration in your MyListCtrl.h file :


int CompareItems(CItemInfo *pItemInfo1, CItemInfo *pItemInfo2);

and the following definition in the MyListCtrl.cpp file :


int CMyListCtrl::CompareItems(CItemInfo *pItemInfo1, CItemInfo *pItemInfo2)
{
	CMyItemInfo *pInfo1 = static_cast(pItemInfo1);
	CMyItemInfo *pInfo2 = static_cast(pItemInfo2);
	int nResult;

	switch (GetSortedColumn()) 
	{
	case 0 : // Sort on column 'Name'
		nResult = pInfo1->GetName().CompareNoCase(pInfo2->GetName());
		break;
	case 1 : // Sort on column 'Number'
	{
		int Number1 = pInfo1->GetNumber();
		int Number2 = pInfo2->GetNumber();

		if (Number1 < Number2)
		{
			nResult = -1;
		}
		else 
		{
			nResult = (Number1 != Number2);
		}
		break;
	}
	default :
		nResult = 0;
		break;
	}
	return IsAscSorted() ? nResult : -nResult;
}

In fact, this function will be called for each pair of item in the list that must be compared and you receive as parameters, the item data attached to each of the element (I will describe below how to attach these ItemInfo to the list items). In this function, you are likely to use two functions from the base class : GetSortedColumn and IsAscSorted which are telling you respectively the column to be sorted and the sort order (ascending or descending). The return value has the same semantic as the callback routine from CListCtrl or the strcmp functions (-1, 0 or 1). Note also the safe static_cast here because CMyItemInfo inherits from CItemInfo.

That's all folks !

Last updated: 6 June 1998



Comments

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: April 22, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Database professionals — whether developers or DBAs — can often save valuable time by learning to get the most from their new or existing productivity tools. Whether you're responsible for managing database projects, performing database health checks and reporting, analyzing code, or measuring software engineering metrics, it's likely you're not taking advantage of some of the lesser-known features of Toad from Dell. Attend this live …

  • Confused by all the agile advice? Relax! With the Agile for Dummies eBook by your side you'll learn the fundamentals of agile and how to increase the productivity of your software teams while enabling them to produce higher-quality solutions that better fulfill customer needs much faster.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds