Improve handling of the borders in MSW dark mode

For Windows dark mode, draw themed borders instead of translating
wxBORDER_THEME to wxBORDER_SIMPLE. Borders have the same thickness
between light mode and dark mode for consistent alignment and
positioning. See #26529.

In dark mode, the border styles wxBORDER_STATIC, wxBORDER_RAISED and
wxBORDER_SUNKEN are sometimes drawn by the system using light mode
colours. So draw these borders similarly to themed borders, but using
the proper width. There is no attempt to mimic a raised or sunken look.

The WM_NCCALCSIZE and WM_NCPAINT message handling is simplified and
corrected for calculating the border width.

Closes #26571.
This commit is contained in:
Steve Cornett
2026-06-10 17:33:42 +02:00
committed by Vadim Zeitlin
parent a36ff353aa
commit 8a573dd7e7
2 changed files with 45 additions and 97 deletions
-27
View File
@@ -2992,33 +2992,6 @@ void wxTextCtrl::MSWSetDarkOrLightMode(SetMode setmode)
::SendMessage(m_hWnd, EM_SETBKGNDCOLOR, 0, wxColourToRGB(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)));
}
#endif
// The text control automatically adds WS_EX_CLIENTEDGE to its style for
// some reason and while this isn't very noticeable in light mode, it
// looks really bad in dark mode, so forcibly remove it unless it was
// explicitly requested.
const auto border = GetBorder();
if ( border != wxBORDER_SUNKEN )
{
const auto exStyle = ::GetWindowLongPtr(m_hWnd, GWL_EXSTYLE);
::SetWindowLongPtr(m_hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_CLIENTEDGE);
}
// When created in dark mode, the text control has a gray border.
// But when switched from light to dark, that border is missing.
// Explicitly enable it by toggling WS_BORDER, unless that was already
// explicitly requested.
if ( wxMSWDarkMode::HasChanged() && border != wxBORDER_SIMPLE )
{
auto style = GetWindowLongPtr(m_hWnd, GWL_STYLE);
if (wxMSWDarkMode::IsActive())
style |= WS_BORDER;
else
style &= ~WS_BORDER;
SetWindowLongPtr(m_hWnd, GWL_STYLE, style);
SetWindowPos(m_hWnd, nullptr, 0, 0, 0, 0,
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
}
}
void wxTextCtrl::MSWUpdateFontOnDPIChange(const wxSize& newDPI)
+45 -70
View File
@@ -1466,14 +1466,6 @@ wxBorder wxWindowMSW::DoTranslateBorder(wxBorder border) const
{
if (border == wxBORDER_THEME)
{
// In dark mode the standard sunken border is too bright, so prefer
// using a simple(r) and darker border instead.
//
// And themed borders don't look good either in dark mode, so don't
// use them in it.
if ( wxMSWDarkMode::IsActive() )
return wxBORDER_SIMPLE;
if (CanApplyThemeBorder())
{
if ( wxUxThemeIsActive() )
@@ -3821,6 +3813,8 @@ wxWindowMSW::MSWHandleMessage(WXLRESULT *result,
// If we want the default themed border then we need to draw it ourselves
case WM_NCCALCSIZE:
{
// The default handling for this message is proper for all
// border styles except wxBORDER_THEME.
if (DoTranslateBorder(GetBorder()) == wxBORDER_THEME)
{
// first ask the widget to calculate the border size
@@ -3840,93 +3834,74 @@ wxWindowMSW::MSWHandleMessage(WXLRESULT *result,
{
rect = (RECT *)lParam;
}
wxUxThemeHandle hTheme((const wxWindow *)this, L"EDIT");
// There is no need to initialize rcClient: either it will
// be done by GetThemeBackgroundContentRect() or we'll do
// it below if it fails.
RECT rcClient;
ClientHDC hdc(GetHwnd());
if ( ::GetThemeBackgroundContentRect
(
hTheme,
hdc,
EP_EDITTEXT,
IsEnabled() ? ETS_NORMAL : ETS_DISABLED,
rect,
&rcClient) != S_OK )
{
// If GetThemeBackgroundContentRect() failed, as can
// happen with at least some custom themes, just use
// the original client rectangle.
rcClient = *rect;
}
InflateRect(&rcClient, -1, -1);
if (wParam)
csparam->rgrc[0] = rcClient;
else
*((RECT*)lParam) = rcClient;
// WVR_REDRAW triggers a bug whereby child windows are moved up and left,
// so don't use.
// rc.result = WVR_REDRAW;
const auto thickness = MSWGetBorderThickness();
InflateRect(rect, -thickness, -thickness);
}
}
break;
case WM_NCPAINT:
{
if (DoTranslateBorder(GetBorder()) == wxBORDER_THEME)
// Determine whether we should draw a border.
bool drawBorder = false;
switch ( DoTranslateBorder(GetBorder()) )
{
case wxBORDER_THEME:
drawBorder = true;
break;
case wxBORDER_STATIC:
case wxBORDER_RAISED:
case wxBORDER_SUNKEN:
// In dark mode, explicitly draw these border styles because
// the default drawing uses light mode colours.
drawBorder = wxMSWDarkMode::IsActive();
break;
case wxBORDER_NONE:
case wxBORDER_SIMPLE:
default:
break;
}
if ( drawBorder )
{
// first ask the widget to paint its non-client area, such as scrollbars, etc.
rc.result = MSWDefWindowProc(message, wParam, lParam);
processed = true;
wxUxThemeHandle hTheme((const wxWindow *)this, L"EDIT");
wxWindowDC dc((wxWindow *)this);
wxMSWDCImpl *impl = (wxMSWDCImpl*) dc.GetImpl();
// Clip the DC so that you only draw on the non-client area
RECT rcBorder;
wxCopyRectToRECT(GetSize(), rcBorder);
RECT rcClient;
const int nState = IsEnabled() ? ETS_NORMAL : ETS_DISABLED;
if ( ::GetThemeBackgroundContentRect
(
hTheme,
GetHdcOf(*impl),
EP_EDITTEXT,
nState,
&rcBorder,
&rcClient
) != S_OK )
{
// As above in WM_NCCALCSIZE, fall back on something
// reasonable for themes which don't implement this
// function.
rcClient = rcBorder;
}
InflateRect(&rcClient, -1, -1);
// Exclude the client area and any scroll bars.
RECT rcClient = rcBorder;
const auto thickness = MSWGetBorderThickness();
InflateRect(&rcClient, -thickness, -thickness);
::ExcludeClipRect(GetHdcOf(*impl), rcClient.left, rcClient.top,
rcClient.right, rcClient.bottom);
// Draw the theme border and background.
// The EDIT theme gives a good general purpose border in light mode.
// There does not seem to be a dark mode EDIT theme that looks good.
// The ListView theme below looks good in dark mode.
wxUxThemeHandle hTheme(this, L"EDIT", L"DarkMode_DarkTheme::ListView");
// Ensure that the part and state we use have the same
// values for both EDIT and ListView.
static_assert((int)EP_EDITTEXT == (int)LVP_LISTITEM, "parts differ?");
static_assert((int)ETS_NORMAL == (int)LISS_NORMAL, "states differ?");
// Make sure the background is in a proper state
if (::IsThemeBackgroundPartiallyTransparent(hTheme, EP_EDITTEXT, nState))
if (::IsThemeBackgroundPartiallyTransparent(hTheme, EP_EDITTEXT, ETS_NORMAL))
{
::DrawThemeParentBackground(GetHwnd(), GetHdcOf(*impl), &rcBorder);
}
// Draw the border
hTheme.DrawBackground(GetHdcOf(*impl), rcBorder, EP_EDITTEXT, nState);
hTheme.DrawBackground(GetHdcOf(*impl), rcBorder, EP_EDITTEXT, ETS_NORMAL);
}
}
break;