From 456449bcc170b588ddb475ed3d1b9c5626fc1214 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 4 Jun 2023 22:49:32 +0100 Subject: [PATCH 1/5] Support wxWinVersion_11 in the private wxGetWinVersion() helper This can be more convenient to use than public functions and it's just better to support all Windows versions in this function too. --- include/wx/msw/private.h | 1 + src/msw/utils.cpp | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/include/wx/msw/private.h b/include/wx/msw/private.h index 4e954904b5..a6838de7e3 100644 --- a/include/wx/msw/private.h +++ b/include/wx/msw/private.h @@ -1007,6 +1007,7 @@ enum wxWinVersion wxWinVersion_8_1 = 0x603, wxWinVersion_10 = 0x1000, + wxWinVersion_11 = 0x1001, // Any version we can't recognize will be later than the last currently // known one, so give it a value greater than any in the known range. diff --git a/src/msw/utils.cpp b/src/msw/utils.cpp index 0d3fe4437d..b0d1c8aa79 100644 --- a/src/msw/utils.cpp +++ b/src/msw/utils.cpp @@ -1095,6 +1095,10 @@ int wxIsWindowsServer() return -1; } +// Windows 11 uses the same version as Windows 10 but its build numbers start +// from 22000, which provides a way to test for it. +static const int FIRST_WINDOWS11_BUILD = 22000; + } // anonymous namespace wxString wxGetOsDescription() @@ -1158,7 +1162,7 @@ wxString wxGetOsDescription() break; case 10: - if (info.dwBuildNumber >= 22000) + if (info.dwBuildNumber >= FIRST_WINDOWS11_BUILD) str = wxIsWindowsServer() == 1 ? "Windows Server 2022" : "Windows 11"; @@ -1269,8 +1273,9 @@ bool wxCheckOsVersion(int majorVsn, int minorVsn, int microVsn) wxWinVersion wxGetWinVersion() { int verMaj, - verMin; - switch ( wxGetOsVersion(&verMaj, &verMin) ) + verMin, + build; + switch ( wxGetOsVersion(&verMaj, &verMin, &build) ) { case wxOS_WINDOWS_NT: switch ( verMaj ) @@ -1305,7 +1310,8 @@ wxWinVersion wxGetWinVersion() break; case 10: - return wxWinVersion_10; + return build >= FIRST_WINDOWS11_BUILD ? wxWinVersion_11 + : wxWinVersion_10; } break; default: From 4cd14831222a779ac77580945831e2a14b38971f Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 4 Jun 2023 23:26:55 +0100 Subject: [PATCH 2/5] Draw all wxListCtrl items ourselves in dark mode Unfortunately drawing just the selected item ourselves and let the control draw all the other ones doesn't work too well as the offsets used for the items differ between Windows versions and so it's difficult to get them right, but getting them wrong is very noticeable as when browsing through the control the current item seems to jump because it's shifted compared to the other ones. So just draw all the items ourselves: this is not as nice as letting Windows do it, but it ensures that they use consistent positions. --- src/msw/listctrl.cpp | 54 ++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/src/msw/listctrl.cpp b/src/msw/listctrl.cpp index f833fd33df..c9995cd5ed 100644 --- a/src/msw/listctrl.cpp +++ b/src/msw/listctrl.cpp @@ -3154,20 +3154,13 @@ void HandleItemPostpaint(NMCUSTOMDRAW nmcd) } } -// Flags for HandleItemPaint() -enum -{ - Paint_Default = 0, - Paint_OnlySelected = 1 -}; - // This function is normally called only if we use custom colours, but it's -// also called when using dark mode to draw the item if it's selected. In this -// case, Paint_OnlySelected is used and we do nothing and return false if the -// item is not selected. +// also called when using dark mode as we have to draw the selected item +// ourselves when using it, and if we do this, we have to paint all the items +// for consistency. // // pLVCD->clrText and clrTextBk should contain the colours to use -bool HandleItemPaint(LPNMLVCUSTOMDRAW pLVCD, HFONT hfont, int flags = 0) +void HandleItemPaint(LPNMLVCUSTOMDRAW pLVCD, HFONT hfont) { NMCUSTOMDRAW& nmcd = pLVCD->nmcd; // just a shortcut @@ -3226,18 +3219,12 @@ bool HandleItemPaint(LPNMLVCUSTOMDRAW pLVCD, HFONT hfont, int flags = 0) pLVCD->clrText = wxColourToRGB(wxSystemSettings::GetColour(syscolFg)); pLVCD->clrTextBk = wxColourToRGB(wxSystemSettings::GetColour(syscolBg)); } - else // not selected - { - if ( flags & Paint_OnlySelected ) - return false; - - // Continue and use normal colours from pLVCD - } + //else: not selected, use normal colours from pLVCD HDC hdc = nmcd.hdc; RECT rc = GetCustomDrawnItemRect(nmcd); - ::SetTextColor(hdc, pLVCD->clrText); + COLORREF colTextOld = ::SetTextColor(hdc, pLVCD->clrText); ::FillRect(hdc, &rc, AutoHBRUSH(pLVCD->clrTextBk)); // we could use CDRF_NOTIFYSUBITEMDRAW here but it results in weird repaint @@ -3249,26 +3236,33 @@ bool HandleItemPaint(LPNMLVCUSTOMDRAW pLVCD, HFONT hfont, int flags = 0) HandleSubItemPrepaint(pLVCD, hfont, colCount); } - HandleItemPostpaint(nmcd); + ::SetTextColor(hdc, colTextOld); - return true; + HandleItemPostpaint(nmcd); } WXLPARAM HandleItemPrepaint(wxListCtrl *listctrl, LPNMLVCUSTOMDRAW pLVCD, wxItemAttr *attr) { + if ( wxMSWDarkMode::IsActive() ) + { + // We need to always paint selected items ourselves as they come + // out completely wrong in DarkMode_Explorer theme, see the comment + // before MSWGetDarkModeSupport(). + pLVCD->clrText = attr && attr->HasTextColour() + ? wxColourToRGB(attr->GetTextColour()) + : wxColourToRGB(listctrl->GetTextColour()); + pLVCD->clrTextBk = attr && attr->HasBackgroundColour() + ? wxColourToRGB(attr->GetBackgroundColour()) + : wxColourToRGB(listctrl->GetBackgroundColour()); + + HandleItemPaint(pLVCD, nullptr); + return CDRF_SKIPDEFAULT; + } + if ( !attr ) { - if ( wxMSWDarkMode::IsActive() ) - { - // We need to always paint selected items ourselves as they come - // out completely wrong in DarkMode_Explorer theme, see the comment - // before MSWGetDarkModeSupport(). - if ( HandleItemPaint(pLVCD, nullptr, Paint_OnlySelected) ) - return CDRF_SKIPDEFAULT; - } - // nothing to do for this item return CDRF_DODEFAULT; } From 9710592cc4c35f60d52257e8d159e1b2448a268c Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 5 Jun 2023 00:24:24 +0100 Subject: [PATCH 3/5] Erase unwanted list view separators in dark mode under Windows 11 For some reason, "DarkMode_Explorer" theme started drawing vertical item separators under Windows 11 even though it did not do this under Windows 10. These separators are unwanted, as they are not shown in the Explorer list control that we try to emulate, and look ugly because they are shifted by 1px compared to the separators drawn by the header control with "ItemsView" theme, which is the only one in which the header looks right in dark mode. So explicitly erase everything below the last item when using dark mode under Windows 11 to get rid of these separators. --- src/msw/listctrl.cpp | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/msw/listctrl.cpp b/src/msw/listctrl.cpp index c9995cd5ed..d0aa49bb6b 100644 --- a/src/msw/listctrl.cpp +++ b/src/msw/listctrl.cpp @@ -3362,7 +3362,21 @@ void wxListCtrl::OnPaint(wxPaintEvent& event) const bool drawHRules = HasFlag(wxLC_HRULES); const bool drawVRules = HasFlag(wxLC_VRULES); - if (!InReportView() || !(drawHRules || drawVRules) || !itemCount) + // Check if we need to do anything ourselves: either draw the rules or, in + // case of using dark mode under Windows 11, erase the unwanted separator + // lines drawn below the items by default, which are ugly because they + // don't align with the separators drawn by the header control. + bool needToDraw = false, + needToErase = false; + if ( InReportView() ) + { + if ( (drawHRules || drawVRules) && itemCount ) + needToDraw = true; + else if ( wxMSWDarkMode::IsActive() && wxGetWinVersion() >= wxWinVersion_11 ) + needToErase = true; + } + + if ( !(needToDraw || needToErase) ) { event.Skip(); return; @@ -3375,10 +3389,6 @@ void wxListCtrl::OnPaint(wxPaintEvent& event) // Reset the device origin since it may have been set dc.SetDeviceOrigin(0, 0); - wxPen pen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT)); - dc.SetPen(pen); - dc.SetBrush(* wxTRANSPARENT_BRUSH); - wxSize clientSize = GetClientSize(); const int countPerPage = GetCountPerPage(); @@ -3391,6 +3401,21 @@ void wxListCtrl::OnPaint(wxPaintEvent& event) const long top = GetTopItem(); const long bottom = wxMin(top + countPerPage, itemCount - 1); + if ( needToErase ) + { + wxRect lastRect; + GetItemRect(bottom, lastRect); + + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(GetBackgroundColour()); + dc.DrawRectangle(0, lastRect.y, clientSize.x, clientSize.y - lastRect.y); + return; + } + + wxPen pen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT)); + dc.SetPen(pen); + dc.SetBrush(* wxTRANSPARENT_BRUSH); + if (drawHRules) { wxRect itemRect; From 15e4f5b1856136b59eb7e955a2042083f8534df4 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 5 Jun 2023 00:38:16 +0100 Subject: [PATCH 4/5] Work around wxListCtrl repainting problems with WS_EX_COMPOSITED It seems that simply creating wxPaintDC, i.e. calling BeginPaint(), ourselves and then letting the native control draw into it, disables whichever optimizations are used by the control that break its drawing with WS_EX_COMPOSITED, so always do this in our OnPaint(), even if we don't need to draw anything ourselves. This allows to avoid disabling WS_EX_COMPOSITED when wxListCtrl is used, which resulted in horrible flicker before. See #23585. --- src/msw/listctrl.cpp | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/src/msw/listctrl.cpp b/src/msw/listctrl.cpp index d0aa49bb6b..8c83aad369 100644 --- a/src/msw/listctrl.cpp +++ b/src/msw/listctrl.cpp @@ -257,12 +257,6 @@ bool wxListCtrl::Create(wxWindow *parent, if ( !MSWCreateControl(WC_LISTVIEW, wxEmptyString, pos, size) ) return false; - // LISTVIEW doesn't redraw correctly when WS_EX_COMPOSITED is used by - // either the control itself (which never happens now, see our overridden - // SetDoubleBuffered()) or even by any of its parents, so we must reset - // this style for them. - MSWDisableComposited(); - const wxVisualAttributes& defAttrs = GetDefaultAttributes(); if ( wxMSWDarkMode::IsActive() ) @@ -373,14 +367,7 @@ void wxListCtrl::MSWInitHeader() void wxListCtrl::MSWAfterReparent() { - // We did it for the original parent in our Create(), but we need to do it - // here for the new one. - MSWDisableComposited(); - - // Ideally we'd re-enable WS_EX_COMPOSITED for the old parent, but this is - // difficult to do correctly, as we'd need to track the number of list - // controls under it instead of just turning it on/off, so for now we don't - // do it. + // This is not used any more, to be removed. } WXDWORD wxListCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const @@ -3355,7 +3342,10 @@ WXLPARAM wxListCtrl::OnCustomDraw(WXLPARAM lParam) return CDRF_DODEFAULT; } -// Necessary for drawing hrules and vrules, if specified +// We need to draw the control ourselves to make it work with WS_EX_COMPOSITED: +// by default, it's not redrawn correctly, apparently due to some optimizations +// used internally, but creating wxPaintDC ourselves seems to be sufficient to +// avoid them, so we do it even if we don't draw anything on it ourselves. void wxListCtrl::OnPaint(wxPaintEvent& event) { const int itemCount = GetItemCount(); @@ -3376,16 +3366,13 @@ void wxListCtrl::OnPaint(wxPaintEvent& event) needToErase = true; } - if ( !(needToDraw || needToErase) ) - { - event.Skip(); - return; - } - wxPaintDC dc(this); wxListCtrlBase::OnPaint(event); + if ( !needToDraw && !needToErase ) + return; + // Reset the device origin since it may have been set dc.SetDeviceOrigin(0, 0); From 4c8ec0ed5deecba1c4f3ebcee797d13b2e14e004 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 5 Jun 2023 14:30:56 +0100 Subject: [PATCH 5/5] Revert "Add wxWindow::MSWAfterReparent() virtual function" This reverts commit cd637663c8 (Add wxWindow::MSWAfterReparent() virtual function, 2022-10-16) and removes wxListCtrl::MSWAfterReparent() added in ff629f3853 (Fix drawing wxListCtrl since enabling double buffering by default, 2022-10-16) which is not used any more now. --- include/wx/msw/listctrl.h | 2 -- include/wx/msw/window.h | 5 ----- src/msw/listctrl.cpp | 5 ----- src/msw/window.cpp | 9 ++------- 4 files changed, 2 insertions(+), 19 deletions(-) diff --git a/include/wx/msw/listctrl.h b/include/wx/msw/listctrl.h index 01d2df34f8..8b6e0ff52d 100644 --- a/include/wx/msw/listctrl.h +++ b/include/wx/msw/listctrl.h @@ -396,8 +396,6 @@ protected: virtual void MSWUpdateFontOnDPIChange(const wxSize& newDPI) override; - virtual void MSWAfterReparent() override; - virtual bool MSWGetDarkModeSupport(MSWDarkModeSupport& support) const override; virtual int MSWGetToolTipMessage() const override; diff --git a/include/wx/msw/window.h b/include/wx/msw/window.h index ad3bc6dd32..1196337007 100644 --- a/include/wx/msw/window.h +++ b/include/wx/msw/window.h @@ -632,11 +632,6 @@ protected: virtual bool DoPopupMenu( wxMenu *menu, int x, int y ) override; #endif // wxUSE_MENUS_NATIVE - // Called by Reparent() after the window parent changes, i.e. GetParent() - // returns the new parent inside this function. - virtual void MSWAfterReparent(); - - // the window handle WXHWND m_hWnd; diff --git a/src/msw/listctrl.cpp b/src/msw/listctrl.cpp index 8c83aad369..e08f99ce35 100644 --- a/src/msw/listctrl.cpp +++ b/src/msw/listctrl.cpp @@ -365,11 +365,6 @@ void wxListCtrl::MSWInitHeader() m_headerCustomDraw->UseHeaderThemeColors(hwndHdr); } -void wxListCtrl::MSWAfterReparent() -{ - // This is not used any more, to be removed. -} - WXDWORD wxListCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const { WXDWORD wstyle = wxListCtrlBase::MSWGetStyle(style, exstyle); diff --git a/src/msw/window.cpp b/src/msw/window.cpp index e1cabfe8e7..ab9ad274f1 100644 --- a/src/msw/window.cpp +++ b/src/msw/window.cpp @@ -1705,17 +1705,12 @@ bool wxWindowMSW::Reparent(wxWindowBase *parent) ::SetParent(hWndChild, hWndParent); - MSWAfterReparent(); - - return true; -} - -void wxWindowMSW::MSWAfterReparent() -{ if ( wxHasWindowExStyle(this, WS_EX_CONTROLPARENT) ) { EnsureParentHasControlParentStyle(GetParent()); } + + return true; } void wxWindowMSW::MSWDisableComposited()