This is a follow-up to commit 0f8a8d2, I tried to handling the scrollbar corner painting for all windows where needed.
I opted for maybe not the prettiest but the simplest possible solution by adding the following code to WM_NCPAINT handler in wxWindow :
diff --git a/src/msw/window.cpp b/src/msw/window.cpp index 900a632f1b..04b0a1baa8 100644 --- a/src/msw/window.cpp +++ b/src/msw/window.cpp @@ -3891,6 +3891,20 @@ wxWindowMSW::MSWHandleMessage(WXLRESULT *result, wxWindowMSW::MSWDrawThemeBorder(hdc); } } + if ( wxMSWDarkMode::IsActive() ) + { + const long style = GetWindowLong(GetHWND(), GWL_STYLE); + + if ( (style & WS_HSCROLL) && (style & WS_VSCROLL) ) + { + if ( !processed ) + { + rc.result = MSWDefWindowProc(message, wParam, lParam); + processed = true; + } + wxMSWImpl::PaintScrollBarCorner(GetHwnd()); + } + } } break;
I then started testing with samples. It seems to work great with controls in many samples (e.g,. treectrl, listctrl, text, or stc); but some samples have visual glitches where drawing the corner seems to collide with the control border.
For example, grid (also e.g. in htmltest or drawing samples) seems to have an extra horizontal line:
Image: wx-corner-grid (view on web)
and the wxScrolled in widgets sample seems to draw an extra corner, where should be none:
Image: wx-corner-widgets (view on web)
Perhaps this depends on a border style...
https://github.com/wxWidgets/wxWidgets/pull/26785
(3 files)
—
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.![]()
The glitch at the lower right is due to PaintScrollBarCorner() subtracting 1 from the size of the rectangle to fill:
—
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.![]()
The glitch at the lower right is due to
PaintScrollBarCorner()subtracting 1 from the size of the rectangle to fill:
Thanks for the reply, the offset was added there to work around visual glitches in the tree and list controls.
Removing it fixes the glitch in most samples, but in some, the border is painted over, here shown in listctrl, treectrl, and stc samples:
Image: wx-dark-corner (view on web)
—
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.![]()
In PaintScrollBarCorner(), the corner square size is computed incorrectly. It should be like this:
diff --git a/src/msw/darkmode.cpp b/src/msw/darkmode.cpp
index 8bbacaef3f..434a9c595d 100644
--- a/src/msw/darkmode.cpp
+++ b/src/msw/darkmode.cpp
@@ -869,9 +869,11 @@ void wxMSWImpl::PaintScrollBarCorner(HWND hwnd)
rectToPaint.left = sbiV.rcScrollBar.left - windowRect.left;
rectToPaint.top = sbiH.rcScrollBar.top - windowRect.top;
- // Constrain outer limits by exactly -1 to snap cleanly to the visual frame edge
- rectToPaint.right = (windowRect.right - windowRect.left) - 1;
- rectToPaint.bottom = (windowRect.bottom - windowRect.top) - 1;
+ auto width = sbiV.rcScrollBar.right - sbiV.rcScrollBar.left;
+ rectToPaint.right = rectToPaint.left + width;
+
+ auto height = sbiH.rcScrollBar.bottom - sbiH.rcScrollBar.top;
+ rectToPaint.bottom = rectToPaint.top + height;
WindowHDC hdcWin(hwnd);
AutoHBRUSH hBrush(RGB(0x17, 0x17, 0x17));
—
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.![]()
In
PaintScrollBarCorner(), the corner square size is computed incorrectly. It should be like this:
Thanks, unfortunately this makes the first issue reported in the original PR resurface: , i.e., after moving frame in the listctrl, treectrl, or stc samples from a high DPI to standard DPI (from 125% to 100%), the border corner is overwritten (looks the same as in the screenshots in my previous post). IIRC, this was the reason for the offset in the original PR.
—
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.![]()
As I said this is probably border width related and the drawing may be adjusted for the border width.
Different controls are created with different default border styles. I have tracked the painted rect sizefor the corner in samples (on 120%DPI): stc, listctrl, treectrl have 22x22 while grid, htmlprinting or drawing have 20x20. This matches the visual differences observed.
This means that we need to take the border width into consideration when drawing the corner. After doing that, I could no longer observe any glitches (but I had done so many tests I may be seeing or not seeing things).
—
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.![]()
@PBfordev pushed 1 commit.
—
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.![]()
So, this is now ready to be tested.
@CEXT-Dan, if you have time, could you please take a look to see if there are not any regressions compared to your version.
—
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.![]()
@PBfordev pushed 1 commit.
—
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.![]()
My previous calculation should be corrected by adjusting for DPI with FromDIP():
auto width = sbiV.rcScrollBar.right - sbiV.rcScrollBar.left;
width = FromDIP(width);
rectToPaint.right = rectToPaint.left + width;
The PaintScrollBarCorner() function should be moved into wxWindowMSW so that FromDIP() is accessible.
The border width should not be taken into account when computing the size of the scrollbar corner. The scrollbar corner is just based on the thickness of the scrollbars. There should be no -1 or other offset.
—
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.![]()
The problem with computing the right and bottom edges of the scrollbar corner based on window width and border width is that the border is not always to the right/bottom of the scrollbar. In the widgets sample go to page Text, select border style simple, select "no wrap" and enter enough text to make the horizontal scrollbar appear:
Image: image (view on web)—
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.![]()
I agree that the computation of the corner rect size should be simple , just using the height of the horizontal and the width of the vertical scrollbar. But in practice, I just can't get it to work.
I tried your code (I find surprising that GetScrollBarInfo() returns the scrollbar coordinates unadjusted for DPI and the previous code still worked), hopefully I got it right:
src/msw/window.cpp | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/msw/window.cpp b/src/msw/window.cpp index 722de3d90a..ad46d66b97 100644 --- a/src/msw/window.cpp +++ b/src/msw/window.cpp @@ -3904,7 +3904,7 @@ wxWindowMSW::MSWHandleMessage(WXLRESULT *result, rc.result = MSWDefWindowProc(message, wParam, lParam); processed = true; } - wxMSWImpl::PaintScrollBarCorner(GetHwnd(), MSWGetBorderThickness()); + MSWDarkPaintScrollBarCorner(); } } } @@ -3966,6 +3966,35 @@ void wxWindowMSW::MSWDrawThemeBorder(WXHDC hdc) } } +void wxWindowMSW::MSWDarkPaintScrollBarCorner() +{ + const HWND hwnd = GetHwnd(); + + WinStruct<SCROLLBARINFO> sbiV, sbiH; + + if ( !::GetScrollBarInfo(hwnd, OBJID_VSCROLL, &sbiV) || + !::GetScrollBarInfo(hwnd, OBJID_HSCROLL, &sbiH) || + (sbiV.rgstate[0] & STATE_SYSTEM_INVISIBLE) || + (sbiH.rgstate[0] & STATE_SYSTEM_INVISIBLE)) + { + return; + } + + const RECT windowRect = wxGetWindowRect(hwnd); + const auto width = FromDIP(sbiV.rcScrollBar.right - sbiV.rcScrollBar.left); + const auto height = FromDIP(sbiH.rcScrollBar.bottom - sbiH.rcScrollBar.top); + RECT rectToPaint; + + rectToPaint.left = sbiV.rcScrollBar.left - windowRect.left; + rectToPaint.top = sbiH.rcScrollBar.top - windowRect.top; + rectToPaint.right = rectToPaint.left + width;
+ rectToPaint.bottom = rectToPaint.top + height;
+ + WindowHDC hdcWin(hwnd); + AutoHBRUSH hBrush(RGB(0x17, 0x17, 0x17)); + ::FillRect(hdcWin, &rectToPaint, hBrush); +} + WXLRESULT wxWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam) { WXLRESULT result;
But I still see the same issue as before with, for example, listctrl and treectrl samples (larger borders?), i.e., the missing corner, but now both in High and normal DPI:
I can reproduce the issue in the widgets sample and text control with a simple border. :( This is a reverse problem to your code where you are missing the corner and I am having an unwanted one in this scenario.
—
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.![]()
I don't see that problem. I think you must have some left over code in the wxListCtrl WM_NCPAINT handling that is interfering.
I replaced the color with red to verify what is being drawn. The placement and size is perfect:
—
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.![]()
I don't see that problem. I think you must have some left over code in the wxListCtrl WM_NCPAINT handling that is interfering.
That would be funny, but unfortunately this is not the case. As can be seen in the commit history, removing that code was the first thing I did (I doublechecked). Moreover, the original code actually worked great for wxListCtrl (it had that -1 offset).
You did not tell me what setup do you use. I have two monitors, the primary (on the right) has DPI set to 120%, the other to 100%. The samples are always launched on the primary one.
FWIW, I test with a CMake-generated MSVS 2026 solution on Windows 11 25H2 build 26200.8875. I restarted my PC between tests to make sure there is not something going on.
It is possible that I messed up adapting your code. A clean branch against master with your suggested code can be found here: master...PBfordev:wxWidgets:corner-stevecor
BTW, the stylus sample still draws the corner in the wrong color. That sample uses scrollbars directly with a wxFrame and we don't get WM_NCPAINT there.
—
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.![]()
It works perfectly without the FromDIP() calls. Windows fooled me. After changing display scaling, you have to sign out and back in for the change to be reflected in the info returned by GetScrollBarInfo(). I did not realize my experiments were invalid.
—
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.![]()
Thanks for the quick response and the code.
However, removing FromDIP() means we went the full circle and we are again missing the corner after moving the window from high to standard DPI screen in those "large border" samples (e.g., listctrl, treectrl, stc). I.e., it looks like this
#26785 (comment)
I have not changed DPI nor light/dark mode, tested after PC restart.
—
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.![]()
Try this:
- const auto width = sbiV.rcScrollBar.right - sbiV.rcScrollBar.left;
- const auto height = sbiH.rcScrollBar.bottom - sbiH.rcScrollBar.top;
+ const auto width = wxGetSystemMetrics(SM_CYHSCROLL, this);
+ const auto height = wxGetSystemMetrics(SM_CYVSCROLL, this);
—
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.![]()
Thanks, after the first round of tests (did not try using different DPIs yet, but I do not expect issues) this looks perfect for all formerly known issues.
Actually, we have just now truly went the full circle. IIRC, the author of the original code did use wxGetSystemMetrics() but I gave them an ill-advised suggestion to not use it since we've already got the scrollbar dimensions from GetScrollInfo(). Little did I know... My bad, sorry.
So now the only remaining (I guess can be left alone for now) is the corner for a frame:
wxFrame itself has scrollbars, but for some reason we do not get WM_NCPAINT for it.wxStyledTextCtrl is the only child of the frame and its scrollbar corner serves as the frame resize gripper. However, we draw over the gripper, so it is not shown.—
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.![]()
@PBfordev pushed 2 commits.
—
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.![]()
@PBfordev pushed 2 commits.
—
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.![]()
Quite the battle for a little square! I tested the List and Tree samples, as well as my routine that’s embedded in AutoCAD. All work 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.![]()
This issue is long, so I will try to sum up the current status of the Battle for the Corner in this post.
The code is now based on the code suggested by @stevecor (thanks for your time, effort, and patience!), i.e., the scrollbar corner size is computed by obtaining the scrollbar dimensions with wxGetSystemMetrics(). Using the function was the only way we made the code work without any visual glitches in all tested scenarios.
But there are still issues, unfortunately.
Firstly, the crossbuild fails because of failing conversion from this (wxWindowMSW*) to const wxWindow* which wxGetSystemMetrics() expects. Not sure why it fails only in that build and what to do about it (aside from an ugly cast).
./src/msw/window.cpp: In member function ‘void wxWindowMSW::MSWDarkPaintScrollBarCorner()’:
./src/msw/window.cpp:3991:77: error: invalid conversion from ‘wxWindowMSW*’ to ‘const wxWindow*’ [-fpermissive]
3991 | rectToPaint.right = rectToPaint.left + wxGetSystemMetrics(SM_CXVSCROLL, this);
| ^~~~
| |
| wxWindowMSW*
In file included from ./include/wx/msw/wrapcctl.h:21,
from ./src/msw/window.cpp:27:
./include/wx/msw/private.h:339:59: note: initializing argument 2 of ‘int wxGetSystemMetrics(int, const wxWindow*)’
339 | extern int wxGetSystemMetrics(int nIndex, const wxWindow* win);
| ~~~~~~~~~~~~~~~~^~~
./src/msw/window.cpp:3992:77: error: invalid conversion from ‘wxWindowMSW*’ to ‘const wxWindow*’ [-fpermissive]
3992 | rectToPaint.bottom = rectToPaint.top + wxGetSystemMetrics(SM_CYHSCROLL, this);
| ^~~~
| |
| wxWindowMSW*
./include/wx/msw/private.h:339:59: note: initializing argument 2 of ‘int wxGetSystemMetrics(int, const wxWindow*)’
339 | extern int wxGetSystemMetrics(int nIndex, const wxWindow* win);
| ~~~~~~~~~~~~~~~~^~~
make: *** [Makefile:27694: coredll_msw_window.o] Error 1
Secondly, there is a regression in case where the control scrollbar corner serves also as the parent frame gripper for resizing. In this case, we paint over the gripper and it is no longer visible. Can be seen in the stc sample. It seems we already attempt to draw the gripper ourselves in dark mode for wxStatusBar and wxDialog?
Thirdly, if the scrollbar corner belongs to wxFrame itself, we don't seem to get WM_NCPAINT, so the corner has the incorrect default color but has the gripper shown. Can be seen in the stylus sample.
Lastly, we use the local hardcoded color for the corner, but this cannot be helped until wxWidgets introduces the API for obtaining all these custom colors used in wxMSW dark mode.
—
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.![]()
This issue is long, so I will try to sum up the current status of the Battle for the Corner in this post.
Thanks!
But there are still issues, unfortunately.
Firstly, the crossbuild fails because of failing conversion from
this(wxWindowMSW*) toconst wxWindow*whichwxGetSystemMetrics()expects. Not sure why it fails only in that build and what to do about it (aside from an ugly cast).
I don't know if this code is needed in wxUniv, I don't remember if it uses native scrollbars or not. If it does not, the simplest fix is to just exclude it from compilation when __WXUNIVERSAL__ is defined.
If it is needed, then we also need an ugly (static) cast because wxWindow in wxUniv is wxWindowUniv which derives from wxWindowMSW but is a different class. Alternatively, wxGetSystemMetrics() could be changed to take wxWindowMSW, which might be a better solution.
Secondly, there is a regression in case where the control scrollbar corner serves also as the parent frame gripper for resizing (this already happens in the GIT master for
wxTreeCtrlandwxListCtrl). In this case, we paint over the gripper and it is no longer visible. Can be seen in the stc sample. It seems we already attempt to draw the gripper ourselves in dark mode forwxStatusBarandwxDialog?
What happens if we don't paint over the gripper, does it have the correct colour or not?
Thirdly, if the scrollbar corner belongs to
wxFrameitself, we don't seem to getWM_NCPAINT, so the corner has the incorrect default color but has the gripper shown. Can be seen in the stylus sample.
I think wxFrame should be getting WM_NCPAINT for it, but I didn't test this...
Lastly, we use the local hardcoded color for the corner, but this cannot be helped until wxWidgets introduces the API for obtaining all these custom colors used in wxMSW dark mode.
wxDarkModeSettings from include/wx/msw/darkmode.h was supposed to be this API. It clearly needs to be extended, however.
—
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.![]()