Add wxWindow::MSWGetBorderThickness() This function is more convenient to use in MSW-specific code than GetWindowBorderSize() as it returns the size of a single border and so doesn't require the caller to divide the return value by 2. No real changes. See #26571.
Take wxWindowMSW parameter in wxUxThemeHandle ctor We don't need wxUniv wxWindow deriving from wxWindowMSW in that build configuration here, just wxWindowMSW is enough and avoids the need to cast wxWindowMSW pointers to wxWindow ones in wxUniv/MSW build.
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.
wxOSX: perform cleanup when applicationWillTerminate: is called Make wxOSX behaviour more similar to wxMSW and ensure that wxApp::OnExit() is called even when the application is being shut down unconditionally. Closes #26542.
Fix wxChoice/wxComboBox popup border in MSW dark mode This was broken by 5bd892f7ac (Fix dark mode selection in list controls in latest Windows 11, 2026-05-31) which styled the entire list as scroll bar which was wrong. Use "DarkMode_DarkTheme" instead of "Explorer" to make the list appear correctly both under Windows 10 and 11. Closes #26573. Closes #26574.
| ... | ... | @@ -207,7 +207,7 @@ public: |
| 207 | 207 | // wxWindow pointer here must be valid and its DPI is always used.
|
| 208 | 208 | // If classesDark is non-nullptr and the dark mode is active, it's used
|
| 209 | 209 | // instead of classes.
|
| 210 | - wxUxThemeHandle(const wxWindow* win,
|
|
| 210 | + wxUxThemeHandle(const wxWindowMSW* win,
|
|
| 211 | 211 | const wchar_t* classes,
|
| 212 | 212 | const wchar_t* classesDark = nullptr);
|
| 213 | 213 |
| ... | ... | @@ -794,6 +794,8 @@ private: |
| 794 | 794 | bool MSWSafeIsDialogMessage(WXMSG* msg);
|
| 795 | 795 | #endif // __WXUNIVERSAL__
|
| 796 | 796 | |
| 797 | + int MSWGetBorderThickness() const;
|
|
| 798 | + |
|
| 797 | 799 | static inline bool MSWIsPositionDirectlySupported(int x, int y)
|
| 798 | 800 | {
|
| 799 | 801 | // The supported coordinate intervals for various functions are:
|
| ... | ... | @@ -225,7 +225,7 @@ void wxChoice::MSWSetDarkOrLightMode(SetMode setmode) |
| 225 | 225 | WinStruct<COMBOBOXINFO> info;
|
| 226 | 226 | if ( ::GetComboBoxInfo(GetHwnd(), &info) && info.hwndList )
|
| 227 | 227 | {
|
| 228 | - wxMSWDarkMode::AllowForWindow(info.hwndList, L"Explorer", L"ScrollBar");
|
|
| 228 | + wxMSWDarkMode::AllowForWindow(info.hwndList, L"DarkMode_DarkTheme");
|
|
| 229 | 229 | }
|
| 230 | 230 | }
|
| 231 | 231 |
| ... | ... | @@ -2992,33 +2992,6 @@ void wxTextCtrl::MSWSetDarkOrLightMode(SetMode setmode) |
| 2992 | 2992 | ::SendMessage(m_hWnd, EM_SETBKGNDCOLOR, 0, wxColourToRGB(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)));
|
| 2993 | 2993 | }
|
| 2994 | 2994 | #endif
|
| 2995 | - |
|
| 2996 | - // The text control automatically adds WS_EX_CLIENTEDGE to its style for
|
|
| 2997 | - // some reason and while this isn't very noticeable in light mode, it
|
|
| 2998 | - // looks really bad in dark mode, so forcibly remove it unless it was
|
|
| 2999 | - // explicitly requested.
|
|
| 3000 | - const auto border = GetBorder();
|
|
| 3001 | - if ( border != wxBORDER_SUNKEN )
|
|
| 3002 | - {
|
|
| 3003 | - const auto exStyle = ::GetWindowLongPtr(m_hWnd, GWL_EXSTYLE);
|
|
| 3004 | - ::SetWindowLongPtr(m_hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_CLIENTEDGE);
|
|
| 3005 | - }
|
|
| 3006 | - |
|
| 3007 | - // When created in dark mode, the text control has a gray border.
|
|
| 3008 | - // But when switched from light to dark, that border is missing.
|
|
| 3009 | - // Explicitly enable it by toggling WS_BORDER, unless that was already
|
|
| 3010 | - // explicitly requested.
|
|
| 3011 | - if ( wxMSWDarkMode::HasChanged() && border != wxBORDER_SIMPLE )
|
|
| 3012 | - {
|
|
| 3013 | - auto style = GetWindowLongPtr(m_hWnd, GWL_STYLE);
|
|
| 3014 | - if (wxMSWDarkMode::IsActive())
|
|
| 3015 | - style |= WS_BORDER;
|
|
| 3016 | - else
|
|
| 3017 | - style &= ~WS_BORDER;
|
|
| 3018 | - SetWindowLongPtr(m_hWnd, GWL_STYLE, style);
|
|
| 3019 | - SetWindowPos(m_hWnd, nullptr, 0, 0, 0, 0,
|
|
| 3020 | - SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
|
|
| 3021 | - }
|
|
| 3022 | 2995 | }
|
| 3023 | 2996 | |
| 3024 | 2997 | void wxTextCtrl::MSWUpdateFontOnDPIChange(const wxSize& newDPI)
|
| ... | ... | @@ -43,7 +43,7 @@ bool wxUxThemeIsActive() |
| 43 | 43 | return s_isActive != 0;
|
| 44 | 44 | }
|
| 45 | 45 | |
| 46 | -wxUxThemeHandle::wxUxThemeHandle(const wxWindow* win,
|
|
| 46 | +wxUxThemeHandle::wxUxThemeHandle(const wxWindowMSW* win,
|
|
| 47 | 47 | const wchar_t* classes,
|
| 48 | 48 | const wchar_t* classesDark)
|
| 49 | 49 | : m_hTheme{DoOpenThemeData(GetHwndOf(win),
|
| ... | ... | @@ -1466,14 +1466,6 @@ wxBorder wxWindowMSW::DoTranslateBorder(wxBorder border) const |
| 1466 | 1466 | {
|
| 1467 | 1467 | if (border == wxBORDER_THEME)
|
| 1468 | 1468 | {
|
| 1469 | - // In dark mode the standard sunken border is too bright, so prefer
|
|
| 1470 | - // using a simple(r) and darker border instead.
|
|
| 1471 | - //
|
|
| 1472 | - // And themed borders don't look good either in dark mode, so don't
|
|
| 1473 | - // use them in it.
|
|
| 1474 | - if ( wxMSWDarkMode::IsActive() )
|
|
| 1475 | - return wxBORDER_SIMPLE;
|
|
| 1476 | - |
|
| 1477 | 1469 | if (CanApplyThemeBorder())
|
| 1478 | 1470 | {
|
| 1479 | 1471 | if ( wxUxThemeIsActive() )
|
| ... | ... | @@ -2274,23 +2266,20 @@ void wxWindowMSW::DoSetClientSize(int width, int height) |
| 2274 | 2266 | }
|
| 2275 | 2267 | }
|
| 2276 | 2268 | |
| 2277 | -wxSize wxWindowMSW::GetWindowBorderSize() const
|
|
| 2269 | +int wxWindowMSW::MSWGetBorderThickness() const
|
|
| 2278 | 2270 | {
|
| 2279 | - wxCoord border;
|
|
| 2280 | 2271 | switch ( GetBorder() )
|
| 2281 | 2272 | {
|
| 2282 | 2273 | case wxBORDER_STATIC:
|
| 2283 | 2274 | case wxBORDER_SIMPLE:
|
| 2284 | - border = 1;
|
|
| 2285 | - break;
|
|
| 2275 | + return 1;
|
|
| 2286 | 2276 | |
| 2287 | 2277 | case wxBORDER_SUNKEN:
|
| 2288 | 2278 | case wxBORDER_THEME:
|
| 2289 | - border = 2;
|
|
| 2290 | - break;
|
|
| 2279 | + return 2;
|
|
| 2291 | 2280 | |
| 2292 | 2281 | case wxBORDER_RAISED:
|
| 2293 | - border = 3;
|
|
| 2282 | + return 3;
|
|
| 2294 | 2283 | break;
|
| 2295 | 2284 | |
| 2296 | 2285 | default:
|
| ... | ... | @@ -2298,9 +2287,13 @@ wxSize wxWindowMSW::GetWindowBorderSize() const |
| 2298 | 2287 | wxFALLTHROUGH;
|
| 2299 | 2288 | |
| 2300 | 2289 | case wxBORDER_NONE:
|
| 2301 | - border = 0;
|
|
| 2290 | + return 0;
|
|
| 2302 | 2291 | }
|
| 2292 | +}
|
|
| 2303 | 2293 | |
| 2294 | +wxSize wxWindowMSW::GetWindowBorderSize() const
|
|
| 2295 | +{
|
|
| 2296 | + const auto border = MSWGetBorderThickness();
|
|
| 2304 | 2297 | return 2*wxSize(border, border);
|
| 2305 | 2298 | }
|
| 2306 | 2299 | |
| ... | ... | @@ -3820,6 +3813,8 @@ wxWindowMSW::MSWHandleMessage(WXLRESULT *result, |
| 3820 | 3813 | // If we want the default themed border then we need to draw it ourselves
|
| 3821 | 3814 | case WM_NCCALCSIZE:
|
| 3822 | 3815 | {
|
| 3816 | + // The default handling for this message is proper for all
|
|
| 3817 | + // border styles except wxBORDER_THEME.
|
|
| 3823 | 3818 | if (DoTranslateBorder(GetBorder()) == wxBORDER_THEME)
|
| 3824 | 3819 | {
|
| 3825 | 3820 | // first ask the widget to calculate the border size
|
| ... | ... | @@ -3839,93 +3834,74 @@ wxWindowMSW::MSWHandleMessage(WXLRESULT *result, |
| 3839 | 3834 | {
|
| 3840 | 3835 | rect = (RECT *)lParam;
|
| 3841 | 3836 | }
|
| 3842 | - |
|
| 3843 | - wxUxThemeHandle hTheme((const wxWindow *)this, L"EDIT");
|
|
| 3844 | - |
|
| 3845 | - // There is no need to initialize rcClient: either it will
|
|
| 3846 | - // be done by GetThemeBackgroundContentRect() or we'll do
|
|
| 3847 | - // it below if it fails.
|
|
| 3848 | - RECT rcClient;
|
|
| 3849 | - |
|
| 3850 | - ClientHDC hdc(GetHwnd());
|
|
| 3851 | - |
|
| 3852 | - if ( ::GetThemeBackgroundContentRect
|
|
| 3853 | - (
|
|
| 3854 | - hTheme,
|
|
| 3855 | - hdc,
|
|
| 3856 | - EP_EDITTEXT,
|
|
| 3857 | - IsEnabled() ? ETS_NORMAL : ETS_DISABLED,
|
|
| 3858 | - rect,
|
|
| 3859 | - &rcClient) != S_OK )
|
|
| 3860 | - {
|
|
| 3861 | - // If GetThemeBackgroundContentRect() failed, as can
|
|
| 3862 | - // happen with at least some custom themes, just use
|
|
| 3863 | - // the original client rectangle.
|
|
| 3864 | - rcClient = *rect;
|
|
| 3865 | - }
|
|
| 3866 | - |
|
| 3867 | - InflateRect(&rcClient, -1, -1);
|
|
| 3868 | - if (wParam)
|
|
| 3869 | - csparam->rgrc[0] = rcClient;
|
|
| 3870 | - else
|
|
| 3871 | - *((RECT*)lParam) = rcClient;
|
|
| 3872 | - |
|
| 3873 | - // WVR_REDRAW triggers a bug whereby child windows are moved up and left,
|
|
| 3874 | - // so don't use.
|
|
| 3875 | - // rc.result = WVR_REDRAW;
|
|
| 3837 | + const auto thickness = MSWGetBorderThickness();
|
|
| 3838 | + InflateRect(rect, -thickness, -thickness);
|
|
| 3876 | 3839 | }
|
| 3877 | 3840 | }
|
| 3878 | 3841 | break;
|
| 3879 | 3842 | |
| 3880 | 3843 | case WM_NCPAINT:
|
| 3881 | 3844 | {
|
| 3882 | - if (DoTranslateBorder(GetBorder()) == wxBORDER_THEME)
|
|
| 3845 | + // Determine whether we should draw a border.
|
|
| 3846 | + bool drawBorder = false;
|
|
| 3847 | + switch ( DoTranslateBorder(GetBorder()) )
|
|
| 3848 | + {
|
|
| 3849 | + case wxBORDER_THEME:
|
|
| 3850 | + drawBorder = true;
|
|
| 3851 | + break;
|
|
| 3852 | + |
|
| 3853 | + case wxBORDER_STATIC:
|
|
| 3854 | + case wxBORDER_RAISED:
|
|
| 3855 | + case wxBORDER_SUNKEN:
|
|
| 3856 | + // In dark mode, explicitly draw these border styles because
|
|
| 3857 | + // the default drawing uses light mode colours.
|
|
| 3858 | + drawBorder = wxMSWDarkMode::IsActive();
|
|
| 3859 | + break;
|
|
| 3860 | + |
|
| 3861 | + case wxBORDER_NONE:
|
|
| 3862 | + case wxBORDER_SIMPLE:
|
|
| 3863 | + default:
|
|
| 3864 | + break;
|
|
| 3865 | + }
|
|
| 3866 | + |
|
| 3867 | + if ( drawBorder )
|
|
| 3883 | 3868 | {
|
| 3884 | 3869 | // first ask the widget to paint its non-client area, such as scrollbars, etc.
|
| 3885 | 3870 | rc.result = MSWDefWindowProc(message, wParam, lParam);
|
| 3886 | 3871 | processed = true;
|
| 3887 | 3872 | |
| 3888 | - wxUxThemeHandle hTheme((const wxWindow *)this, L"EDIT");
|
|
| 3889 | 3873 | wxWindowDC dc((wxWindow *)this);
|
| 3890 | 3874 | wxMSWDCImpl *impl = (wxMSWDCImpl*) dc.GetImpl();
|
| 3891 | - |
|
| 3892 | - // Clip the DC so that you only draw on the non-client area
|
|
| 3893 | 3875 | RECT rcBorder;
|
| 3894 | 3876 | wxCopyRectToRECT(GetSize(), rcBorder);
|
| 3895 | 3877 | |
| 3896 | - RECT rcClient;
|
|
| 3878 | + // Exclude the client area and any scroll bars.
|
|
| 3879 | + RECT rcClient = rcBorder;
|
|
| 3880 | + const auto thickness = MSWGetBorderThickness();
|
|
| 3881 | + InflateRect(&rcClient, -thickness, -thickness);
|
|
| 3882 | + ::ExcludeClipRect(GetHdcOf(*impl), rcClient.left, rcClient.top,
|
|
| 3883 | + rcClient.right, rcClient.bottom);
|
|
| 3897 | 3884 | |
| 3898 | - const int nState = IsEnabled() ? ETS_NORMAL : ETS_DISABLED;
|
|
| 3885 | + // Draw the theme border and background.
|
|
| 3899 | 3886 | |
| 3900 | - if ( ::GetThemeBackgroundContentRect
|
|
| 3901 | - (
|
|
| 3902 | - hTheme,
|
|
| 3903 | - GetHdcOf(*impl),
|
|
| 3904 | - EP_EDITTEXT,
|
|
| 3905 | - nState,
|
|
| 3906 | - &rcBorder,
|
|
| 3907 | - &rcClient
|
|
| 3908 | - ) != S_OK )
|
|
| 3909 | - {
|
|
| 3910 | - // As above in WM_NCCALCSIZE, fall back on something
|
|
| 3911 | - // reasonable for themes which don't implement this
|
|
| 3912 | - // function.
|
|
| 3913 | - rcClient = rcBorder;
|
|
| 3914 | - }
|
|
| 3915 | - |
|
| 3916 | - InflateRect(&rcClient, -1, -1);
|
|
| 3887 | + // The EDIT theme gives a good general purpose border in light mode.
|
|
| 3888 | + // There does not seem to be a dark mode EDIT theme that looks good.
|
|
| 3889 | + // The ListView theme below looks good in dark mode.
|
|
| 3890 | + wxUxThemeHandle hTheme(this, L"EDIT", L"DarkMode_DarkTheme::ListView");
|
|
| 3917 | 3891 | |
| 3918 | - ::ExcludeClipRect(GetHdcOf(*impl), rcClient.left, rcClient.top,
|
|
| 3919 | - rcClient.right, rcClient.bottom);
|
|
| 3892 | + // Ensure that the part and state we use have the same
|
|
| 3893 | + // values for both EDIT and ListView.
|
|
| 3894 | + static_assert((int)EP_EDITTEXT == (int)LVP_LISTITEM, "parts differ?");
|
|
| 3895 | + static_assert((int)ETS_NORMAL == (int)LISS_NORMAL, "states differ?");
|
|
| 3920 | 3896 | |
| 3921 | 3897 | // Make sure the background is in a proper state
|
| 3922 | - if (::IsThemeBackgroundPartiallyTransparent(hTheme, EP_EDITTEXT, nState))
|
|
| 3898 | + if (::IsThemeBackgroundPartiallyTransparent(hTheme, EP_EDITTEXT, ETS_NORMAL))
|
|
| 3923 | 3899 | {
|
| 3924 | 3900 | ::DrawThemeParentBackground(GetHwnd(), GetHdcOf(*impl), &rcBorder);
|
| 3925 | 3901 | }
|
| 3926 | 3902 | |
| 3927 | 3903 | // Draw the border
|
| 3928 | - hTheme.DrawBackground(GetHdcOf(*impl), rcBorder, EP_EDITTEXT, nState);
|
|
| 3904 | + hTheme.DrawBackground(GetHdcOf(*impl), rcBorder, EP_EDITTEXT, ETS_NORMAL);
|
|
| 3929 | 3905 | }
|
| 3930 | 3906 | }
|
| 3931 | 3907 | break;
|
| ... | ... | @@ -238,6 +238,17 @@ void wxBell() |
| 238 | 238 | - (void)applicationWillTerminate:(NSNotification *)application {
|
| 239 | 239 | wxUnusedVar(application);
|
| 240 | 240 | wxTheApp->OSXOnWillTerminate();
|
| 241 | + |
|
| 242 | + // Cocoa will `exit(0)` soon after returning from
|
|
| 243 | + // `applicationWillTerminate:`, without another opportunity for cleanup, so
|
|
| 244 | + // do it here.
|
|
| 245 | + |
|
| 246 | + // Ignore the return code. It's probably better to let Cocoa do the rest of
|
|
| 247 | + // its cleanup like normal rather than calling `exit()` here, and the
|
|
| 248 | + // return code doesn't mean much, if anything, for macOS usually.
|
|
| 249 | + (void)wxTheApp->CallOnExit();
|
|
| 250 | + |
|
| 251 | + wxEntryCleanup();
|
|
| 241 | 252 | }
|
| 242 | 253 | |
| 243 | 254 | - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
|
—
View it on GitLab.
You're receiving this email because of your account on gitlab.com. Manage all notifications · Help