Listview with Colored Column Headers

Hi everybody,

I want to create a listview with custom column header colors and font. The background and text color that this control comes with is not applicalble to the column headers. After several hours in Google, this was the closest example I came up with: http://www.codeproject.com/Articles/2890/Using-ListView-control-under-Win32-API . This example shows how cutomize the items in the listview; not the column headers. Nonetheless, I tried following this example, but in the "ProcessCustomDraw(...)" function, my code goes no farther than case CDDS_PREPAINT. Can someone point me in the right direction? Here is part of my code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     HDC         hdc;
     PAINTSTRUCT ps;
	 int wmId, wmEvent;

	 switch(message)
	 {
		case WM_CREATE:
		{
			create_objects(hWnd);
			break;
		}

		case WM_NOTIFY:
		{
			switch(LOWORD(wParam))
			{
				case ID_LST_VIEW:
				{
					LPNMLISTVIEW pnm = (LPNMLISTVIEW)lParam;
					if(pnm->hdr.code == NM_CUSTOMDRAW)
					{
						SetWindowLong(hWnd, DWL_MSGRESULT, (LONG)ProcessCustomDraw(lParam));
						return TRUE;
					}
					break;
				}
			}

			break;
		}
                .
                .
                .
[code]



The ProcessCustomDraw function as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
LRESULT ProcessCustomDraw (LPARAM lParam)
{
	LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)lParam;

	switch(lplvcd->nmcd.dwDrawStage)
	{
		case CDDS_PREPAINT:
			return CDRF_NOTIFYITEMDRAW;
		case CDDS_ITEMPREPAINT:
			return CDRF_NOTIFYSUBITEMDRAW;

		case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
		{
			if(lplvcd->iSubItem == 0)
			{
				//SelectObject(lplvcd->nmcd.dwItemSpec, lplvcd->nmcd.lItemlParam)

				lplvcd->clrText   = RGB(255,255,255);
				lplvcd->clrTextBk = RGB(240,55,23);
				return CDRF_NEWFONT;
			}
			else if(lplvcd->iSubItem == 1)
			{
				lplvcd->clrText   = RGB(255,255,0);
				lplvcd->clrTextBk = RGB(0,0,0);
				return CDRF_NEWFONT;
			}
			else if(lplvcd->iSubItem == 2)
			{
				lplvcd->clrText   = RGB(20,26,158);
				lplvcd->clrTextBk = RGB(200,200,10);
				return CDRF_NEWFONT;
			}
			else if(lplvcd->iSubItem == 3)
			{
				lplvcd->clrText   = RGB(12,15,46);
				lplvcd->clrTextBk = RGB(200,200,200);
				return CDRF_NEWFONT;
			}
			else if(lplvcd->iSubItem == 4)
			{
				lplvcd->clrText   = RGB(120,0,128);
				lplvcd->clrTextBk = RGB(20,200,200);
				return CDRF_NEWFONT;
			}
			else if(lplvcd->iSubItem == 5)
			{
				lplvcd->clrText   = RGB(255,255,255);
				lplvcd->clrTextBk = RGB(0,0,150);
				return CDRF_NEWFONT;
			}
		}
		break;
	}
	return CDRF_DODEFAULT;
}


The code compiles and runs, but it does not color the entries because it never reaches case CDDS_SUBITEM | CDDS_ITEMPREPAINT.

Thanks in advance,
DominicanJB
While you can use the custom draw mechanism to customize column headers of a List View control, it is a bit more complicated than colouring the list view's items.

1. The column headers are displayed by child control of the list view -- a Header control

Header Control
http://msdn.microsoft.com/en-us/library/windows/desktop/bb775239%28v=vs.85%29.aspx

2. You can get a handle to the header control using the LVM_GETHEADER message or the ListView_GetHeader macro, but...

3. As WM_NOTIFY messages are sent to a control's parent, it's the list view's WndProc which will be receiving the NM_CUSTOMDRAW notifications you need to handle. So you will need to subclass the list view (SetWindowSubclass, etc) so you can get at the message (either handle it in the subclass proc, or maybe forward it on to the list view's parent for handling there.)

4. Custom draw for Header controls works with the NMCUSTOMDRAW structure, not the NMLVCUSTOMDRAW.
http://msdn.microsoft.com/en-us/library/windows/desktop/bb775483%28v=vs.85%29.aspx

This doesn't allow you to just specify the colours (using clrText, clrTextBk, clrFace) you want and then leave it to the control to do the drawing. But you can use SetTextColor and SetBkColor to set the color the display context will use, which will change the colour of the text and button face, but not the button edges.

5. If you need to change the color of the button edges you will have to draw the button yourself.

Andy

Last edited on
Andy,

Thanks for your post. Based on your comments, this what I have so far. It compiles, but still does not work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
bool create_column_headers(HWND hWnd)
{
	HWND hList = GetDlgItem(hWnd, ID_LST_VIEW);               //Get the handle of the list view
	LVCOLUMN lvCol;                                           //Define/declare the col struct
	memset(&lvCol,0,sizeof(lvCol));                           //Reset Coluom
	lvCol.mask    = LVCF_TEXT|LVCF_WIDTH|LVCF_SUBITEM;        //Type of mask
	lvCol.cx      = 0x28;                                     //Width of each field
	lvCol.pszText = L"Item";                                  //First Header Text
	lvCol.fmt     = LVCFMT_CENTER;                            //Center-justified  
	SendMessage(hList, LVM_INSERTCOLUMN, 0, (LPARAM)&lvCol);  //Add the item via SendMessage fucntion
	//We repeate the same proc for however many items we need
	const TCHAR* ListViewItems[] = { TEXT( "Sub Item1" ), TEXT( "Sub Item2" ), TEXT( "Sub Item3" ), TEXT( "Sub Item4" ), TEXT( "Sub Item5" ) };
	lvCol.cx      = 0x42;
	for(int a = 1; a <= 5; a++)
	{
		lvCol.pszText = (LPWSTR) ListViewItems[a-1];
		SendMessage(hList, LVM_INSERTCOLUMN, a, (LPARAM)&lvCol);
	}

	//Get the window handle of the header list
	HWND hHeader = (HWND) SendMessage(hList,  LVM_GETHEADER, 0, 0);  //Works 

	//Since the header list is child to hList, we need to sub class it,
	//so that it can handle its own message
	if(hHeader != NULL)
	{
		WNDPROC hProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr(hHeader, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(HeaderProc)));
		if(OriginalEditCtrlProc == NULL) {OriginalEditCtrlProc = hProc;}
	}
	
	return true;
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
LRESULT ProcessHeaderCustomDraw(LPARAM lParam)
{
	 LPNMCUSTOMDRAW lplvcd = (LPNMCUSTOMDRAW) lParam;
	 switch(lplvcd->dwDrawStage)
	 {
		case CDDS_PREPAINT:
			return CDRF_NOTIFYITEMDRAW;

		case CDDS_ITEMPREPAINT:
			return CDRF_NOTIFYSUBITEMDRAW;

		case CDDS_SUBITEM | CDDS_ITEMPREPAINT:
			SetBkColor(lplvcd->hdc, RGB(0x00, 0xFF, 0x00));
			return CDRF_NEWFONT;
	 }
	 return CDRF_DODEFAULT;

}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
LRESULT CALLBACK HeaderProc(HWND hWnd,  UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if(uMsg == WM_NOTIFY)
	{
		switch(LOWORD(wParam))
		{
			case ID_LST_VIEW:
			{
				LPNMLISTVIEW pnm = (LPNMLISTVIEW)lParam;
				if(pnm->hdr.code == NM_CUSTOMDRAW)
				{
					SetWindowLong(hWnd, DWL_MSGRESULT, (LONG)ProcessHeaderCustomDraw(lParam));
					return TRUE;
				}
				break;
			}
		}
	}
	return CallWindowProc(OriginalEditCtrlProc, hWnd, uMsg, wParam, lParam);
}


When I debug the code, the subclass doesn't get a WM_NOTIFY message. I'm new to the concept of subclassing, I'm not sure if this is the proper way of doing it.

Thanks again,

DominicanJB
#1 Unless you need to support Windows 2000 you should be using SetWindowSubclass (which was introduced in Windows XP) rather than the antique SetWindowLongPtr plus GWLP_WNDPROC approach.

Subclassing Controls / Subclassing Controls Using ComCtl32.dll version 6
http://msdn.microsoft.com/en-us/library/windows/desktop/bb773183%28v=vs.85%29.aspx#subclassing_v6

SetWindowSubclass function
http://msdn.microsoft.com/en-us/library/windows/desktop/bb762102%28v=vs.85%29.aspx

Etc

#2 You need to subclass the list, not the header control

As I said before, the WM_NOTIFY message is sent to the parent of the header control, which is the list.

#3 The list control is not a dialog, so you need to follow the rules of normal WndProcs rather than DlgProcs in the subclass proc. That is, return the message specific return code; here you should be returning whatever often ProcessHeaderCustomDraw returns

You should (must) not use SetWindowLongPtr and DWL_MSGRESULT; it is only for use with dialogs.

Andy
Last edited on
Topic archived. No new replies allowed.