Merge branch 'listctrl-dark-win11'

Fixes for wxListCtrl drawing under Windows 11 in dark mode and drawing
when using WS_EX_COMPOSITED.

See #23608.
This commit is contained in:
Vadim Zeitlin 2023-06-05 15:32:58 +02:00
commit d80887f302
6 changed files with 70 additions and 74 deletions

View file

@ -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;

View file

@ -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.

View file

@ -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;

View file

@ -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() )
@ -371,18 +365,6 @@ void wxListCtrl::MSWInitHeader()
m_headerCustomDraw->UseHeaderThemeColors(hwndHdr);
}
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.
}
WXDWORD wxListCtrl::MSWGetStyle(long style, WXDWORD *exstyle) const
{
WXDWORD wstyle = wxListCtrlBase::MSWGetStyle(style, exstyle);
@ -3154,20 +3136,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 +3201,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 +3218,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;
}
@ -3361,30 +3337,40 @@ 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();
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() )
{
event.Skip();
return;
if ( (drawHRules || drawVRules) && itemCount )
needToDraw = true;
else if ( wxMSWDarkMode::IsActive() && wxGetWinVersion() >= wxWinVersion_11 )
needToErase = true;
}
wxPaintDC dc(this);
wxListCtrlBase::OnPaint(event);
if ( !needToDraw && !needToErase )
return;
// 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();
@ -3397,6 +3383,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;

View file

@ -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:

View file

@ -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()