MSW: Fix Darkmode NoneClient border flicker via GDI region partitioning (PR #26635)

17 views
Skip to first unread message

Mohmed abdel-fattah

unread,
Jun 26, 2026, 12:03:39 PM (2 days ago) Jun 26
to wx-...@googlegroups.com, Subscribed

Fixes #26630
Closes #26631

  • Invert WM_NCPAINT execution flow to draw custom dark borders before DefProc runs.
  • Construct a clipped inner region to pass to MSWDefWindowProc, preventing the system from flashing the light-mode border.
  • Implement strict GDI validation using GetObjectType to safely screen out internal window manager sentinels (like wParam == 1).
  • Migrate from high-level wxWindowDC/wxMSWDCImpl abstraction to low-level GetDCEx to achieve fine-grained control over region clipping flags.
  • Duplicate the region handle because the OS assumes absolute ownership under the DCX_INTERSECTRGN flag and destroys it during ReleaseDC.
  • Explicitly exclude active scrollbar bounds via GetScrollBarInfo to prevent border bleed.
  • Integrate DrawDarkModeEdge to render custom 3D raised and sunken frames using multi-tone dark-mode color structures, maintaining clear control depth without relying on system themes.
  • before

https://github.com/user-attachments/assets/64629375-1a0c-49ea-b974-0f5b2d8c2a63

  • after

https://github.com/user-attachments/assets/b02640f4-dbce-4a94-a438-bfa20faab7e5

Screenshot.2026-06-26.185453.png (view on web)

You can view, comment on, or merge this pull request online at:

  https://github.com/wxWidgets/wxWidgets/pull/26635

Commit Summary

  • 4f8e9e2 MSW: Fix dark mode NC border flicker via GDI region partitioning
  • 6b6e394 Add Missing DrawDarkModeEdge in darkmode.h header

File Changes

(3 files)

Patch Links:


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!
You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26635@github.com>

Mohmed abdel-fattah

unread,
Jun 26, 2026, 1:09:55 PM (2 days ago) Jun 26
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.

  • d5cf101 Fix initialized/uninitialized build errors.


View it on GitHub or unsubscribe.


Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26635/before/6b6e3943b23031868573697b645c125dfaf717a9/after/d5cf101f29194ddb7e43d7f8274a9241676a0c27@github.com>

Steve Cornett

unread,
Jun 26, 2026, 1:36:15 PM (2 days ago) Jun 26
to wx-...@googlegroups.com, Subscribed

@stevecor commented on this pull request.


In src/msw/window.cpp:

>                      RECT rcBorder;
                     wxCopyRectToRECT(GetSize(), rcBorder);
+                    if ((border == wxBORDER_RAISED || border == wxBORDER_SUNKEN) && wxMSWDarkMode::IsActive())

Can we use wxMSWDarkMode::DrawDarkModeEdge() for all the dark mode border styles, and not just these two? The border drawn by DrawThemeParentBackground() does not look good on Windows prior to build 26200. On 26200, the border looks basically the same as wxBORDER_SIMPLE no matter the style, which isn't great.


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26635/review/4581467069@github.com>

VZ

unread,
Jun 27, 2026, 1:44:54 PM (21 hours ago) Jun 27
to wx-...@googlegroups.com, Subscribed

@vadz commented on this pull request.

Thanks, this is a lot of code but I guess it's unavoidable.

It would be nice to tidy up this code to use proper types and RAII helpers, please let me know if you plan to do it.


In include/wx/msw/private/darkmode.h:

> @@ -45,6 +45,13 @@ void AllowForWindow(HWND hwnd,
                     const wchar_t* themeName = L"Explorer",
                     const wchar_t* themeId = nullptr);
 
+// Draws a raised or sunken border using two shades (light/dark)
+// for dark mode. The rectangle `rc` is in device coordinates (e.g., window).
+// Draws a raised or sunken border with three shades (light, mid, dark)
+// to mimic the classic Windows 3D effect, using dark-mode colours.
+// The rectangle `rc` is in device coordinates (e.g., the full window).
+void DrawDarkModeEdge(HDC hdc, const RECT& rc, int borderStyle, int thickness);

This should take parameter of proper type:

⬇️ Suggested change
-void DrawDarkModeEdge(HDC hdc, const RECT& rc, int borderStyle, int thickness);
+void DrawDarkModeEdge(HDC hdc, const RECT& rc, wxBorder borderStyle, int thickness);

In src/msw/window.cpp:

> @@ -3835,7 +3835,8 @@ wxWindowMSW::MSWHandleMessage(WXLRESULT *result,
             {
                 // Determine whether we should draw a border.
                 bool drawBorder = false;
-                switch ( DoTranslateBorder(GetBorder()) )
+                int border = DoTranslateBorder(GetBorder());
⬇️ Suggested change
-                int border = DoTranslateBorder(GetBorder());
+                wxBorder border = DoTranslateBorder(GetBorder());

(maybe also make it const).


In src/msw/window.cpp:

>                      {
-                        ::DrawThemeParentBackground(GetHwnd(), GetHdcOf(*impl), &rcBorder);
+                        // For flat styles (wxBORDER_THEME, wxBORDER_STATIC, wxBORDER_SIMPLE)
+                        // Keep your themed drawing:

This is weird, who is "you" here?


In src/msw/window.cpp:

>                      }
 
-                    // Draw the border
-                    hTheme.DrawBackground(GetHdcOf(*impl), rcBorder, EP_EDITTEXT, ETS_NORMAL);
+                    ReleaseDC(hwnd, dc);
+
+                    /* Call default proc with our Clip Riogn to get the scrollbars etc. also painted */

Typo

⬇️ Suggested change
-                    /* Call default proc with our Clip Riogn to get the scrollbars etc. also painted */
+                    /* Call default proc with our clip region to get the scrollbars etc. also painted */

In src/msw/window.cpp:

> -                    processed = true;
+                    HWND hwnd = GetHWND();
+                    RECT rcWin, rcClient;
+                    RECT  rcVscroll = {};
+                    RECT rcHscroll = {};
+                    ::GetWindowRect(hwnd, &rcWin);
+                    ::GetClientRect(hwnd, &rcClient); // Get the client area dimensions.
+                    const auto thickness = MSWGetBorderThickness();
+                    RECT rcClip = rcWin;
+                    rcClip.left += thickness;
+                    rcClip.top += thickness;
+                    rcClip.right -= thickness;
+                    rcClip.bottom -= thickness;
+
+                    /* New clipping region passed to default proc to exclude border */
+                    HRGN cliprgn = CreateRectRgnIndirect(&rcClip);

Should use AutoHRGN for this one.


In src/msw/darkmode.cpp:

> +    HBRUSH brushOuterTL = CreateSolidBrush(colTopLeftOuter);
+    HBRUSH brushInner = CreateSolidBrush(clrMid);
+    HBRUSH brushOuterBR = CreateSolidBrush(colBottomRightOuter);

These really should be AutoHBRUSHs.


In src/msw/darkmode.cpp:

> +    // For sunken edges, the colours are reversed: top/left use shadow,
+    // bottom/right use highlight, and the middle band is still mid-tone

Mohmed abdel-fattah

unread,
8:58 AM (1 hour ago) 8:58 AM
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.

  • 16ab49b 🚀Renamed DrawDarkModeEdge to DrawDarkModeBorder, updated the comments and Extended support to all border styles:


View it on GitHub or unsubscribe.


Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26635/before/d5cf101f29194ddb7e43d7f8274a9241676a0c27/after/16ab49bce245c7ea71d003eba69f72d5e09853a4@github.com>

Mohmed abdel-fattah

unread,
9:04 AM (1 hour ago) 9:04 AM
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.

  • 54d0726 Add missing vsstyle header


View it on GitHub or unsubscribe.


Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26635/before/16ab49bce245c7ea71d003eba69f72d5e09853a4/after/54d072645609335ab4dc0cfaa56885d89e16d3bb@github.com>

Mohmed abdel-fattah

unread,
9:05 AM (1 hour ago) 9:05 AM
to wx-...@googlegroups.com, Subscribed

@memoarfaa commented on this pull request.


In include/wx/msw/private/darkmode.h:

> @@ -45,6 +45,13 @@ void AllowForWindow(HWND hwnd,
                     const wchar_t* themeName = L"Explorer",
                     const wchar_t* themeId = nullptr);
 
+// Draws a raised or sunken border using two shades (light/dark)
+// for dark mode. The rectangle `rc` is in device coordinates (e.g., window).
+// Draws a raised or sunken border with three shades (light, mid, dark)
+// to mimic the classic Windows 3D effect, using dark-mode colours.
+// The rectangle `rc` is in device coordinates (e.g., the full window).
+void DrawDarkModeEdge(HDC hdc, const RECT& rc, int borderStyle, int thickness);

this is done.


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26635/review/4587564801@github.com>

Mohmed abdel-fattah

unread,
9:06 AM (1 hour ago) 9:06 AM
to wx-...@googlegroups.com, Subscribed

@memoarfaa commented on this pull request.


In src/msw/window.cpp:

> @@ -3835,7 +3835,8 @@ wxWindowMSW::MSWHandleMessage(WXLRESULT *result,
             {
                 // Determine whether we should draw a border.
                 bool drawBorder = false;
-                switch ( DoTranslateBorder(GetBorder()) )
+                int border = DoTranslateBorder(GetBorder());

this also


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26635/review/4587565319@github.com>

Mohmed abdel-fattah

unread,
9:07 AM (1 hour ago) 9:07 AM
to wx-...@googlegroups.com, Subscribed

@memoarfaa commented on this pull request.


In src/msw/darkmode.cpp:

> +    HBRUSH brushOuterTL = CreateSolidBrush(colTopLeftOuter);
+    HBRUSH brushInner = CreateSolidBrush(clrMid);
+    HBRUSH brushOuterBR = CreateSolidBrush(colBottomRightOuter);

this now RAII helpers.


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26635/review/4587566160@github.com>

Mohmed abdel-fattah

unread,
9:07 AM (1 hour ago) 9:07 AM
to wx-...@googlegroups.com, Subscribed

@memoarfaa commented on this pull request.


In src/msw/darkmode.cpp:

> +    // For sunken edges, the colours are reversed: top/left use shadow,
+    // bottom/right use highlight, and the middle band is still mid-tone

Mohmed abdel-fattah

unread,
9:08 AM (1 hour ago) 9:08 AM
to wx-...@googlegroups.com, Subscribed

@memoarfaa commented on this pull request.


In src/msw/window.cpp:

> -                    processed = true;
+                    HWND hwnd = GetHWND();
+                    RECT rcWin, rcClient;
+                    RECT  rcVscroll = {};
+                    RECT rcHscroll = {};
+                    ::GetWindowRect(hwnd, &rcWin);
+                    ::GetClientRect(hwnd, &rcClient); // Get the client area dimensions.
+                    const auto thickness = MSWGetBorderThickness();
+                    RECT rcClip = rcWin;
+                    rcClip.left += thickness;
+                    rcClip.top += thickness;
+                    rcClip.right -= thickness;
+                    rcClip.bottom -= thickness;
+
+                    /* New clipping region passed to default proc to exclude border */
+                    HRGN cliprgn = CreateRectRgnIndirect(&rcClip);

this done


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26635/review/4587567498@github.com>

Mohmed abdel-fattah

unread,
9:31 AM (1 hour ago) 9:31 AM
to wx-...@googlegroups.com, Subscribed
memoarfaa left a comment (wxWidgets/wxWidgets#26635)

@vadz @stevecorThanks for the feedback!
1- Renamed to DrawDarkModeBorder() and extended support to all border styles (SIMPLE, STATIC, THEME, RAISED, SUNKEN).
2- Improved GDI resource management with AutoHRGN.
3- I also added RAII helpers (wxPaintDCEx, off-screen wxMemoryDC buffering, etc.) in the dark mode painting path to avoid flicker and ensure clean resource management.
4- see attached use-case diagram

drawdarkmodeeedge_state_matrix.png (view on web)

Regarding the focus ring: some native controls (e.g. internal tab page editors, and similar) should skip drawing the extra focus ring.

https://github.com/user-attachments/assets/d8db6adc-4efc-4c00-8f12-97f554d4386f


Reply to this email directly, view it on GitHub, or unsubscribe.
Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26635/c4826249105@github.com>

Mohmed abdel-fattah

unread,
10:24 AM (1 minute ago) 10:24 AM
to wx-...@googlegroups.com, Push

@memoarfaa pushed 1 commit.

  • 0f28b77 Apply suggestions from code review


View it on GitHub or unsubscribe.


Triage notifications, keep track of coding agent tasks and review pull requests on the go with GitHub Mobile for iOS and Android. Download it today!

You are receiving this because you are subscribed to this thread.Message ID: <wxWidgets/wxWidgets/pull/26635/before/54d072645609335ab4dc0cfaa56885d89e16d3bb/after/0f28b77e70d0b6ee716963e8dedaf60c3b370a3c@github.com>

Reply all
Reply to author
Forward
0 new messages