Problem with extended CHeaderCtrl


I've created a class that extends the CHeaderCtrl class, for use with my class that extends the CListCtrl class.

The problem I'm having is that the header items don't paint their background in anything but white.

The MESSAGE MAP has:

 
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CCheckHeadCtrl::OnCustomDraw)


and this is the OnCustomDraw method, which is being called and the header item text is being set to the colour I'm setting, if I change the colour for the text to another RGB value the text is drawn in that colour.

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
void CCheckHeadCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
	NMCUSTOMDRAW* pCD = (NMCUSTOMDRAW*)pNMHDR;
	DWORD dwDrawStage,
			dwItemSpec;

	*pResult = CDRF_DODEFAULT;

	dwDrawStage = pCD->dwDrawStage;
	dwItemSpec	= pCD->dwItemSpec;

	if (dwDrawStage == CDDS_PREPAINT)
	{
		*pResult = CDRF_NOTIFYITEMDRAW;
	}

	if (dwDrawStage == CDDS_ITEMPREPAINT)
	{
		HDC hDC = pCD->hdc;
		SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

		if (dwItemSpec)
			SetBkColor(hDC, RGB(  0,   0, 255));	// Blue
		else
			SetBkColor(hDC, RGB(  255,   0, 0));	// Red

		*pResult = CDRF_NEWFONT;
	}
}


I would appreciate if anyone could assist to get the header items background to paint in a colour other than white.
Your code colors the first item red and all the rest blue, as expected.

I created a dialog-based project and added a CListCtrl to the dialog resource using the Resource Editor.

I created a class CCheckHeadCtrl which was derived from CHeaderCtrl and added your single method to it.

I then added two member variable to the main dialog class (CListCtrl and CCheckHeadCtrl) and hooked them up as usual. Also adding a few columns to the list control.

And it just worked!

I then tweaked your code to be a bit more chic !!

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
void CCheckHeadCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
	NMCUSTOMDRAW* pCD = (NMCUSTOMDRAW*)pNMHDR;
	//DWORD dwDrawStage,
	//      dwItemSpec;

	*pResult = CDRF_DODEFAULT;

	DWORD dwDrawStage = pCD->dwDrawStage;
	DWORD dwItemSpec  = pCD->dwItemSpec;

	if (dwDrawStage == CDDS_PREPAINT)
	{
		*pResult = CDRF_NOTIFYITEMDRAW;
	}

	if (dwDrawStage == CDDS_ITEMPREPAINT)
	{
		HDC hDC = pCD->hdc;

		const COLORREF crefBlue  = RGB(  0,   0, 255);
		const COLORREF crefWhite = RGB(255, 255, 255);
		const COLORREF crefRed   = RGB(255,   0,   0);
		const COLORREF crefBlack = RGB(  0,   0,   0);

		COLORREF crefBkColor = crefBlue;

		switch(dwItemSpec % 3)
		{
			case 0: crefBkColor = crefBlue ; break;
			case 1: crefBkColor = crefWhite; break;
			case 2: crefBkColor = crefRed  ; break;
		}

		COLORREF crefTextColor = crefBlack;

		static const int nThreshold = 100;
		int nContrast = ColorContrast(crefBkColor);
		if(nThreshold > nContrast)
		{
			crefTextColor = crefWhite;
		}

		SetTextColor(hDC, crefTextColor);
		SetBkColor(hDC, crefBkColor);

		*pResult = CDRF_NEWFONT;
	}
}


where

1
2
3
4
5
6
7
8
9
// http://www.w3.org/TR/AERT#color-contrast
// ((R X 299) + (G X 587) + (B X 114)) / 1000
inline int
ColorContrast(COLORREF crefValue)
{
	return ( (GetRValue(crefValue) * 299)
		   + (GetGValue(crefValue) * 587)
		   + (GetBValue(crefValue) * 114) ) / 1000;
}


and the header items now go blue, white, red, blue, white, red, ...

Andy
Last edited on

Thank you for your reply. My CCheckHeadCtrl::OnCustomDraw() function is being called but the background isn't being coloured in like yours. I must have missed somthing somewhere.

Here's my code:

CheckHeadCtrl.h:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma once

class CCheckHeadCtrl : public CHeaderCtrl
{
public:
	CCheckHeadCtrl();
	virtual ~CCheckHeadCtrl();

protected:
	//{{AFX_MSG(CCheckHeadCtrl)
	afx_msg void OnItemClicked(NMHDR* pNMHDR, LRESULT* pResult);
	afx_msg void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult);
		// NOTE - the ClassWizard will add and remove member functions here.
	//}}AFX_MSG
	
	DECLARE_MESSAGE_MAP()

private:
};


CheckHeadCtrl.cpp:
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include "stdafx.h"
#include "CheckHeadCtrl.h"



CCheckHeadCtrl::CCheckHeadCtrl()
{
}


CCheckHeadCtrl::~CCheckHeadCtrl()
{
}


BEGIN_MESSAGE_MAP(CCheckHeadCtrl, CHeaderCtrl)
	//{{AFX_MSG_MAP(CCheckHeadCtrl)
	ON_NOTIFY_REFLECT(HDN_ITEMCLICK, &CCheckHeadCtrl::OnItemClicked)
	ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CCheckHeadCtrl::OnCustomDraw)
//	ON_WM_ERASEBKGND()
		// NOTE - the ClassWizard will add and remove mapping macros here.
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()



void CCheckHeadCtrl::OnItemClicked(NMHDR* pNMHDR, LRESULT* pResult)
{
	NMHEADER* pNMHead = (NMHEADER*)pNMHDR;
	*pResult = 0;

	int nItem = pNMHead->iItem;
	if (0 != nItem)
		return;

	HDITEM hdItem;
	hdItem.mask = HDI_IMAGE;
	VERIFY( GetItem(nItem, &hdItem) );

	if (hdItem.iImage == 1)
		hdItem.iImage = 2;
	else
		hdItem.iImage = 1;

	VERIFY(SetItem(nItem, &hdItem));
	
	BOOL bl = hdItem.iImage == 2 ? TRUE : FALSE;
	CListCtrl* pListCtrl = (CListCtrl*)GetParent();
	int nCount = pListCtrl->GetItemCount();

	for(nItem = 0; nItem < nCount; nItem++)
	{
		ListView_SetCheckState(pListCtrl->GetSafeHwnd(), nItem, bl);
	}
}


void CCheckHeadCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
	NMCUSTOMDRAW* pCD = (NMCUSTOMDRAW*)pNMHDR;
	DWORD dwDrawStage,
			dwItemSpec;

	*pResult = CDRF_DODEFAULT;

	dwDrawStage = pCD->dwDrawStage;
	dwItemSpec	= pCD->dwItemSpec;

	if (dwDrawStage == CDDS_PREPAINT)
	{
		*pResult = CDRF_NOTIFYITEMDRAW;
	}

	if (dwDrawStage == CDDS_ITEMPREPAINT)
	{
		HDC hDC = pCD->hdc;
		SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

		if (dwItemSpec)
			SetBkColor(hDC, RGB(  0,   0, 255));	// Blue
		else
			SetBkColor(hDC, RGB(  255,   0, 0));	// Red

		*pResult = CDRF_NEWFONT;
	}
}


CheckListCtrl.h:
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
#pragma once

#include "CheckHeadCtrl.h"

class CCheckListCtrl : public CListCtrl
{
public:
	CCheckListCtrl();
	virtual ~CCheckListCtrl();

	BOOL Init();

protected:
	CCheckHeadCtrl	m_checkHeadCtrl;

	//{{AFX_MSG(CCheckListCtrl)
	afx_msg void OnItemChanged(NMHDR* pNMHDR, LRESULT* pResult);		
	//}}AFX_MSG

	DECLARE_MESSAGE_MAP()

private:
	CImageList	m_checkImgList;
	CFont			m_Font;
	BOOL			m_blInited;
};


CheckListCtrl.cpp:
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include "stdafx.h"
#include "CheckListCtrl.h"
#include "resource.h"


CCheckListCtrl::CCheckListCtrl() 
	: m_blInited(FALSE)
{
}


CCheckListCtrl::~CCheckListCtrl()
{
	m_Font.DeleteObject();
}


BEGIN_MESSAGE_MAP(CCheckListCtrl, CListCtrl)
	//{{AFX_MSG_MAP(CCheckListCtrl)
	ON_NOTIFY_REFLECT(LVN_ITEMCHANGED, OnItemChanged)		
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


BOOL CCheckListCtrl::Init()
{
	if (m_blInited)
		return TRUE;

	CHeaderCtrl* pHeadCtrl = this->GetHeaderCtrl();
	ASSERT(pHeadCtrl->GetSafeHwnd());

	VERIFY(m_checkHeadCtrl.SubclassWindow(pHeadCtrl->GetSafeHwnd()));
	VERIFY(m_checkImgList.Create(IDB_CHECKBOXES, 16, 3, RGB(255,0,255)));
	int i = m_checkImgList.GetImageCount();
	m_checkHeadCtrl.SetImageList(&m_checkImgList);
	
	HDITEM hdItem;
	hdItem.mask = HDI_IMAGE | HDI_FORMAT;
	VERIFY(m_checkHeadCtrl.GetItem(0, &hdItem));
	hdItem.iImage = 1;
	hdItem.fmt |= HDF_IMAGE;
	
	VERIFY(m_checkHeadCtrl.SetItem(0, &hdItem));

	m_blInited = TRUE;

	return TRUE;
}


void CCheckListCtrl::OnItemChanged(NMHDR* pNMHDR, LRESULT* pResult)
{
	NMLISTVIEW* pNMLV = (NMLISTVIEW*)pNMHDR;
	*pResult = 0;

	if (pNMLV && LVIF_STATE == pNMLV->uChanged)
	{
		BOOL blAllChecked = TRUE;
		int nCount = GetItemCount();

		for(int nItem = 0; nItem < nCount; nItem++)
		{
			if ( !ListView_GetCheckState(GetSafeHwnd(), nItem) )
			{
				blAllChecked = FALSE;
				break;
			}
		}
		
		HDITEM hdItem;
		hdItem.mask = HDI_IMAGE;
		if (blAllChecked)
			hdItem.iImage = 2;
		else
			hdItem.iImage = 1;
		VERIFY( m_checkHeadCtrl.SetItem(0, &hdItem) );
	}
}


Then in the header of my CDialog derived class I have a member variable declared for the derived CListCtrl:

 
CCheckListCtrl	m_fileslist;


and in the derived CDialog::OnInitDialog():

1
2
3
4
5
6
7
8
9
10
11
12
13
      CDialogEx::OnInitDialog();
...
	m_fileslist.GetClientRect(&rc);
	m_fileslist.InsertColumn(_name, "Name", LVCFMT_LEFT, (int)(rc.Width() * 0.5));
	m_fileslist.InsertColumn(_date, "Date modified", LVCFMT_LEFT, (int)(rc.Width() * 0.25));
	m_fileslist.InsertColumn(_size, "Size", LVCFMT_LEFT, (int)(rc.Width() * 0.25));
	m_fileslist.InsertColumn(_filepath, "", LVCFMT_LEFT, -1);

	m_fileslist.ModifyStyle(0, LVS_REPORT, NULL);
	m_fileslist.SetExtendedStyle(m_fileslist.GetExtendedStyle() | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_CHECKBOXES | LVS_OWNERDATA);

	m_fileslist.Init();
...


I would appreciate your helpful advice on where I have gone wrong. Many thanks.
Last edited on

I've resolved the issue. I initially setup a test project consisting of a CDialog on which I placed a ListCtrl, added my code and subclassed the CHeaderCtrl like @andywestken did, this then coloured the background of the header control as expected.

I then re-created my project and all works too. When I initially created my project I had put the CListCtrl in a group-control and had then deleted that group-control. I think this was the cause of the issue, but I don't know why it stopped the background of the subclassed CHeaderCtrl from having it's background colour set even when the group-control had been deleted from the project?
Ah OK...

The reason you're code was working my my PC but not mine is that I'd disabled the UI themes. When I tried my app on my laptop your problem occured for me.

To get it to work you have to paint the background yourself, as shown here:

Changing MFC List Control header color
http://stackoverflow.com/questions/28766659/changing-mfc-list-control-header-color

But to get it to worked properly (e.g. depress when clicked, change color when cursor hovering over, etc.) will involve more work than sample shows.

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