Unable to use NM_CUSTOMDRAW for radios / checkboxes in a child CMFCPropertySheet / CMFCPropertyPage

ChuckieAJ 181 Reputation points
2025-02-08T14:21:59.67+00:00

I am trying to add a dark mode theme to derived CMFCPropertySheet class. There are a couple of issues, but I will restrict this to one of them.

The problem I have is related to the resolution provided here about rendering checkboxes and radios:

https://learn.microsoft.com/en-us/answers/questions/2154261/drawing-check-box-and-radio-controls-on-a-cdialoge

This is what it looks like at the moment:

User's image

As you can see, the checkboxes and radios have the property page background colour, and black text and white background. But they are operational:

User's image

I am handling OnNotify, in both my derived sheet and page classes, just like I do for derived CDialog classes. Snippet:

		NMHDR* pNMHDR = reinterpret_cast<NMHDR*>(lParam);
		if (pNMHDR->code == NM_CUSTOMDRAW)
		{
			LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);
			*pResult = CDRF_DODEFAULT;

			if (pNMCD->dwDrawStage == CDDS_PREPAINT)
			{
				if (!pParentWnd)
					return FALSE;

				auto pControl = pParentWnd->GetDlgItem(static_cast<int>(pNMHDR->idFrom));
				if (pControl)
				{
					TCHAR className[256];
					GetClassName(pControl->GetSafeHwnd(), className, 256);

					if (_tcscmp(className, L"Button") == 0)
					{
						CButton* pButton = reinterpret_cast<CButton*>(pControl);
						if (pButton)
						{
							DWORD buttonStyle = pButton->GetStyle();
							if ((buttonStyle & BS_CHECKBOX) == BS_CHECKBOX ||
								(buttonStyle & BS_AUTOCHECKBOX) == BS_AUTOCHECKBOX ||
								(buttonStyle & BS_RADIOBUTTON) == BS_RADIOBUTTON ||
								(buttonStyle & BS_AUTORADIOBUTTON) == BS_AUTORADIOBUTTON)
							{
								CDC* pDC = CDC::FromHandle(pNMCD->hdc);
								if (pDC)
								{
									auto savedDC = SaveDC(pNMCD->hdc);
									pDC->SetBkMode(TRANSPARENT);

									// Check if the control is disabled
									if (!pControl->IsWindowEnabled())
									{
										// Set text color to grey for disabled controls
										pDC->SetTextColor(GetSysColor(COLOR_GRAYTEXT));
									}
									else
									{
										// Set text color to dark text for enabled controls
										pDC->SetTextColor(DarkModeTools::kDarkTextColor);
									}

									SIZE sizeCheckBox;
									HTHEME hTheme = OpenThemeData(nullptr, L"Button");
									if (hTheme)
									{
										GetThemePartSize(hTheme, pNMCD->hdc, BP_CHECKBOX, CBS_UNCHECKEDNORMAL, NULL, TS_DRAW, &sizeCheckBox);
										CloseThemeData(hTheme);
									}
									else
									{
										sizeCheckBox.cx = 16; // Fallback size
										sizeCheckBox.cy = 16;
									}

									SIZE sizeExtent;
									GetTextExtentPoint32(pNMCD->hdc, L"0", 1, &sizeExtent);
									int nShift = sizeExtent.cx / 2;

									RECT rc = pNMCD->rc;
									OffsetRect(&rc, sizeCheckBox.cx + nShift, 0);

									CString strText;
									pControl->GetWindowText(strText);
									pDC->DrawText(strText, -1, &rc, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_EXPANDTABS | DT_END_ELLIPSIS);
									RestoreDC(pNMCD->hdc, savedDC);
									*pResult = CDRF_SKIPDEFAULT;
									return TRUE;
								}
							}
						}
					}
				}
			}
		}

I can confirm that I can trace in as far as:

if ((buttonStyle & BS_CHECKBOX) == BS_CHECKBOX || 
	(buttonStyle & BS_AUTOCHECKBOX) == BS_AUTOCHECKBOX || 
	(buttonStyle & BS_RADIOBUTTON) == BS_RADIOBUTTON || 
	(buttonStyle & BS_AUTORADIOBUTTON) == BS_AUTORADIOBUTTON)

But it never appears to satisfy that if test, even though we have checkbox/radio controls on the property pages.

C++
C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
3,852 questions
{count} votes

1 answer

Sort by: Most helpful
  1. ChuckieAJ 181 Reputation points
    2025-02-11T14:28:37.5033333+00:00

    I have at long last found the root causes of this conundrum!

    Page Class

    I needed to handle WM_ERASEBKGND message in my derived property page:

    BOOL DarkModeTools::CDarkModeMFCPropertyPage::OnEraseBkgnd(CDC* pDC)
    {
    	// Get the client area rectangle.
    	CRect rc;
    	GetClientRect(rc);
    
    	// Fill the client area with the dark background color.
    	pDC->FillSolidRect(rc, DarkModeTools::kDarkSheetBackgroundColor);
    
    	// Indicate that the background has been handled.
    	return true;
    
    	// Alternatively, call the base class method:
    	// return __super::OnEraseBkgnd(pDC);
    }
    
    

    This ensures that we have the correct background. It is most likely the only change that I needed to make.

    Handling Back / Text Colours

    I have a common function that I call from everywhere when handling WM_CTL_COLOR, and I realised that I needed to cater for both regular background and property sheet background colours

    	inline void ApplyDarkModeColors(CDC* pDC, CWnd* pControl, UINT nCtlColor, const bool parentIsChild = false)
    	{
    		const auto backColour = parentIsChild ? DarkModeTools::kDarkSheetBackgroundColor
    											  : DarkModeTools::kDarkBackgroundColor;
    		switch (nCtlColor)
    		{
    		case CTLCOLOR_STATIC: // Static text
    			pDC->SetBkColor(backColour);
    			if (pControl->IsWindowEnabled())
    			{
    				pDC->SetTextColor(kDarkTextColor);
    			}
    			else
    			{
    				pDC->SetTextColor(::GetSysColor(COLOR_GRAYTEXT));
    			}
    			break;
    		case CTLCOLOR_DLG: // Dialog background
    		case CTLCOLOR_SCROLLBAR: // Scrollbars
    		case CTLCOLOR_EDIT: // Edit control
    		case CTLCOLOR_LISTBOX: // Listbox
    		case CTLCOLOR_MSGBOX:
    			pDC->SetTextColor(kDarkTextColor);
    			pDC->SetBkColor(backColour);
    			break;
    		case CTLCOLOR_BTN: // Buttons
    		default:
    			// You can add specific cases here if needed
    			break;
    		}
    	}
    

    Similar colour logic had to be applied to the NM_CUSTOMDRAW too. Now it looks great!

    User's image

    1 person found this answer helpful.
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.