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!