WinapiZone.net




Tutorials > WinAPI > Listview > Column sort image

How to display the sort order in the listview columns



Older versions of Windows: Windows 98, ME, 2000

Windows XP and superior

The way you can display the sort order in the column can change under different Windows versions. More exactly, it depends on the different common controls version you (implicitly) load: the 6.0 version (the latest one) or versions inferior to the 6.0. In fact, with commctrl version 6, displaying the column order is as easy as setting a flag. Under Windows 9x/ME/2000, instead, you need to manually set an image in the header. This tutorial's code works in both cases. Let's see how to do it.



Is the user using the latest common control version?

So, we need to check if the application is using the latest commctrl.dll version. Here's how to test it:

To compile this function you need to include the header shlwapi.h as stated in the DllGetVersion page from MSDN.
BOOL IsCommCtrlVersion6() { static BOOL isCommCtrlVersion6 = -1; if (isCommCtrlVersion6 != -1) return isCommCtrlVersion6; //The default value isCommCtrlVersion6 = FALSE; HINSTANCE commCtrlDll = LoadLibrary(_T("comctl32.dll")); if (commCtrlDll) { DLLGETVERSIONPROC pDllGetVersion; pDllGetVersion = (DLLGETVERSIONPROC)GetProcAddress(commCtrlDll, "DllGetVersion"); if (pDllGetVersion) { DLLVERSIONINFO dvi = {0}; dvi.cbSize = sizeof(DLLVERSIONINFO); (*pDllGetVersion)(&dvi); isCommCtrlVersion6 = (dvi.dwMajorVersion == 6); } FreeLibrary(commCtrlDll); } return isCommCtrlVersion6; }

Let's dig into the code. All the common Windows DLLs exports a function called DllGetVersion, that gets the version of the DLL it was loaded from: in fact we can only load it dynamically. The DLLVERSIONINFO structure passed to it will then contain the version divided in three vars (major, minor and build version). The only thing that matters here is the major version, since all the old version are inferior to 6. Finally, since this function will be called many times, the result is stored in a static variable.

The function

As said before, the older versions of Windows needs an image to be set manually in the header: two images, actually, a down and an up arrow bitmaps. These two bitmaps are available in the download section of this page. It's your choice where to put them: i've included them in the executable and i use LoadImage and IDs to load those images. Change it if you put them elsewhere.

Here's the complete function code:
void ListView_SetHeaderSortImage(HWND listView, int columnIndex, BOOL isAscending) { HWND header = ListView_GetHeader(listView); BOOL isCommonControlVersion6 = IsCommCtrlVersion6(); int columnCount = Header_GetItemCount(header); for (int i = 0; i<columnCount; i++) { HDITEM hi = {0}; hi.mask = HDI_FORMAT | (isCommonControlVersion6 ? 0 : HDI_BITMAP); Header_GetItem(header, i, &hi); //Set sort image to this column if (i == columnIndex) { if (isCommonControlVersion6) { hi.fmt &= ~(HDF_SORTDOWN|HDF_SORTUP); hi.fmt |= isAscending ? HDF_SORTUP : HDF_SORTDOWN; } else { UINT bitmapID = isAscending ? IDB_UPARROW : IDB_DOWNARROW; //If there's a bitmap, let's delete it. if (hi.hbm) DeleteObject(hi.hbm); hi.fmt |= HDF_BITMAP|HDF_BITMAP_ON_RIGHT; hi.hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(bitmapID), IMAGE_BITMAP, 0,0, LR_LOADMAP3DCOLORS); } } //Remove sort image (if exists) //from other columns. else { if (isCommonControlVersion6) hi.fmt &= ~(HDF_SORTDOWN|HDF_SORTUP); else< { //If there's a bitmap, let's delete it. if (hi.hbm) DeleteObject(hi.hbm); hi.mask &= ~HDI_BITMAP; hi.fmt &= ~(HDF_BITMAP|HDF_BITMAP_ON_RIGHT); } } Header_SetItem(header, i, &hi); } }

The function, explained

The function has this prototype:
void ListView_SetHeaderSortImage(HWND listView, int columnIndex, BOOL isAscending);
So, whenever you call this function, you must pass: The LVN_COLUMNCLICK notification handler should be the right place to put this function.

Inside the function now:
HWND header = ListView_GetHeader(listView);
BOOL isCommonControlVersion6 = IsCommCtrlVersion6();
The LVCOLUMN structure is useless, because it doesn't let you set all the header's features. So i'm going to use the header messages (HDM_X) sent to the header window, obtained through ListView_GetHeader. I store the BOOL that is TRUE or FALSE whether the application has commctrl version 6 loaded, to avoid unnecessary overhead by calling the function more than once inside this function.

int columnCount = Header_GetItemCount(header); for (int i = 0; i<columnCount; i++) { //... }
The function doesn't store neither require from you the last ordered column index, so i need to iterate through all the existing columns to add the image to the correct column index and remove it from all the other column. And, since there are always few columns, this loop doesn't halt your program.

HDITEM hi = {0}; hi.mask = HDI_FORMAT | (isCommonControlVersion6 ? 0 : HDI_BITMAP); Header_GetItem(header, i, &hi);
Here i retrieve the column props from the header through Header_GetItem. With commctrl 6, the only thing that changes is the format; without it i need to retrieve and set the bitmap too. So,
if (i == columnIndex) { if (isCommonControlVersion6) { hi.fmt &= ~(HDF_SORTDOWN|HDF_SORTUP); hi.fmt |= isAscending ? HDF_SORTUP : HDF_SORTDOWN; } else { UINT bitmapID = isAscending ? IDB_UPARROW : IDB_DOWNARROW; //If there's a bitmap, let's delete it. if (hi.hbm) DeleteObject(hi.hbm); hi.fmt |= HDF_BITMAP|HDF_BITMAP_ON_RIGHT; hi.hbm = (HBITMAP)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(bitmapID), IMAGE_BITMAP, 0,0, LR_LOADMAP3DCOLORS); } }
The column index corresponds to the index you passed to the function, so this column is being sorted. As before, two ways:
else { if (isCommonControlVersion6) hi.fmt &= ~(HDF_SORTDOWN|HDF_SORTUP); else { //If there's a bitmap, let's delete it. if (hi.hbm) DeleteObject(hi.hbm); hi.mask &= ~HDI_BITMAP; hi.fmt &= ~(HDF_BITMAP|HDF_BITMAP_ON_RIGHT); } }
All the other column indexex aren't being sorted, so i remove the image/flags from them (even if they don't have it).

Header_SetItem(header, i, &hi);
Finally, i apply the changes through Header_SetItem and the modified HDITEM structure.

From the download section on this page you can download an example or a demo to test this function with your hands.

Comments

Bai Nan wrote:

Thank you very much!
I have tried to adapt your idea for the CListCtrl and it works well.
(24.01.2010, 07:46)

EVIL wrote:

Thank you very much! :) I was glad to hear that I don't need to manually set an image to a header :)
P.S. I also utilized ListView_SetSelectedColumn (>= v6) for my purposes. Now there's not only an arrow appearing at the header of the sorted column, but the column itself is highlighting! Well, it's actually darkening, but it improves visibility anyway.
(14.10.2009, 12:17)

valery wrote:

Thanks, that's what I need!
(02.10.2009, 17:05)

Tamil wrote:

Superb work.
coding can we use in Windows Mobile 6.0 ?
(04.12.2008, 07:40)

Doug wrote:

Please disregard my previous post. Looks like it's a bug with a CListCtrl subclass specific to our application.
(02.12.2008, 19:54)

Doug wrote:

For me this works fine for the leftmost column, but the image does not appear any other columns. Any idea what might be going wrong?
(02.12.2008, 00:13)

Ed wrote:

Under XP in EXE it works fine, in DLL the header text and image disappear when clicking the header items. Are there any special steps to get this working in a DLL? Any suggestions?
(17.09.2008, 03:13)

Karsten wrote:

Thanx a lot
(17.07.2008, 13:29)

Igor P. wrote:

Hi! Under Win2000/XP working fine, but under Win98/ME header text and image disappears, when clicking on headers.
(24.04.2008, 20:29)

Paul wrote:

Brilliant! Thanks very much.
(25.09.2007, 14:39)

Your comment:

Name:
E-mail or homepage:
Captcha:
captcha

Script by Alex



This page was last modified 29 months, 3 weeks, 5 days and 6 hours ago