From 67e9829b55b72f480abbed64195667a0233fd87b Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Thu, 25 Mar 2021 14:46:37 +0100 Subject: [PATCH 1/5] Remove unneeded wxClientDC wxDateTimePickerCtrl::DoGetBestSize() Just call GetTextExtent() on the window itself, it does the same thing anyhow. No real changes, just a tiny simplification. --- src/msw/datetimectrl.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/msw/datetimectrl.cpp b/src/msw/datetimectrl.cpp index 41059fa360..25a6ba97d3 100644 --- a/src/msw/datetimectrl.cpp +++ b/src/msw/datetimectrl.cpp @@ -162,8 +162,6 @@ void wxDateTimePickerCtrl::SetNullText(const wxString& text) wxSize wxDateTimePickerCtrl::DoGetBestSize() const { - wxClientDC dc(const_cast(this)); - // Use the same native format as the underlying native control. #if wxUSE_INTL wxString s = wxDateTime::Now().Format(wxLocale::GetOSInfo(MSWGetFormat())); @@ -177,7 +175,7 @@ wxSize wxDateTimePickerCtrl::DoGetBestSize() const // the width of the month string varies a lot, so try to account for it s += wxS("W"); - wxSize size = dc.GetTextExtent(s); + wxSize size = GetTextExtent(s); // We can ask the control itself to compute its ideal size, but we only use // it for the horizontal component: the vertical size is not computed From 4f6cf6da5b7da23e8d757865745a104e7f675c55 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Thu, 25 Mar 2021 14:49:54 +0100 Subject: [PATCH 2/5] Don't call GetTextExtent() if we don't use its result It's unnecessary to call GetTextExtent() just to discard/overwrite its result immediately with the value returned from DTM_GETIDEALSIZE, so don't do it. This reverts another part of a98d8448fa (Fix size of wxDateTimePickerCtrl after DPI change, 2019-01-13) which wasn't done in 7de85d7470 (Restore correct best width of wxDatePickerCtrl in MSW, 2020-05-24).included This commit is best viewed ignoring whitespace-only changes. --- src/msw/datetimectrl.cpp | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/msw/datetimectrl.cpp b/src/msw/datetimectrl.cpp index 25a6ba97d3..aaf8b80dc3 100644 --- a/src/msw/datetimectrl.cpp +++ b/src/msw/datetimectrl.cpp @@ -162,20 +162,7 @@ void wxDateTimePickerCtrl::SetNullText(const wxString& text) wxSize wxDateTimePickerCtrl::DoGetBestSize() const { - // Use the same native format as the underlying native control. -#if wxUSE_INTL - wxString s = wxDateTime::Now().Format(wxLocale::GetOSInfo(MSWGetFormat())); -#else // !wxUSE_INTL - wxString s("XXX-YYY-ZZZZ"); -#endif // wxUSE_INTL/!wxUSE_INTL - - // the best size for the control is bigger than just the string - // representation of the current value because the control must accommodate - // any date and while the widths of all digits are usually about the same, - // the width of the month string varies a lot, so try to account for it - s += wxS("W"); - - wxSize size = GetTextExtent(s); + wxSize size; // We can ask the control itself to compute its ideal size, but we only use // it for the horizontal component: the vertical size is not computed @@ -192,9 +179,25 @@ wxSize wxDateTimePickerCtrl::DoGetBestSize() const && ::SendMessage(m_hWnd, DTM_GETIDEALSIZE, 0, (LPARAM)&idealSize) ) { size.x = idealSize.cx; + size.y = GetCharHeight(); } - else // Adjust the size ourselves. + else // Compute the size ourselves. { + // Use the same native format as the underlying native control. +#if wxUSE_INTL + wxString s = wxDateTime::Now().Format(wxLocale::GetOSInfo(MSWGetFormat())); +#else // !wxUSE_INTL + wxString s("XXX-YYY-ZZZZ"); +#endif // wxUSE_INTL/!wxUSE_INTL + + // the best size for the control is bigger than just the string + // representation of the current value because the control must accommodate + // any date and while the widths of all digits are usually about the same, + // the width of the month string varies a lot, so try to account for it + s += wxS("W"); + + size = GetTextExtent(s); + // Account for the drop-down arrow or spin arrows. size.x += wxSystemSettings::GetMetric(wxSYS_HSCROLL_ARROW_X, m_parent); From 8ceca690644745c4631c922087df3a1b74b76c63 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Thu, 25 Mar 2021 14:53:29 +0100 Subject: [PATCH 3/5] Always handle wxDP_ALLOWNONE ourselves in best size computation DTM_GETIDEALSIZE doesn't seem to account for it, so we need to do it ourselves even when using it. Closes #19120. --- src/msw/datetimectrl.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/msw/datetimectrl.cpp b/src/msw/datetimectrl.cpp index aaf8b80dc3..ee90f3a476 100644 --- a/src/msw/datetimectrl.cpp +++ b/src/msw/datetimectrl.cpp @@ -200,12 +200,15 @@ wxSize wxDateTimePickerCtrl::DoGetBestSize() const // Account for the drop-down arrow or spin arrows. size.x += wxSystemSettings::GetMetric(wxSYS_HSCROLL_ARROW_X, m_parent); - - // We need to account for the checkbox, if we have one. - if ( MSWAllowsNone() ) - size.x += 3 * GetCharWidth(); } + // We need to account for the checkbox, if we have one -- DTM_GETIDEALSIZE + // doesn't take it into account (actually, it somehow returns _smaller_ + // size when using DTS_SHOWNONE than when not using it, which doesn't make + // any sense at all). + if ( MSWAllowsNone() ) + size.x += 3 * GetCharWidth(); + int scrollY = wxSystemSettings::GetMetric(wxSYS_HSCROLL_ARROW_Y, m_parent); size.y = wxMax(size.y, scrollY); From aa50c6d8294913ba933f9687aa2bccda5b94769f Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 27 Mar 2021 21:55:13 +0100 Subject: [PATCH 4/5] Further improve best size of wxDatePickerCtrl with wxDP_ALLOWNONE Compute the best size more precisely to be exactly compatible with the control appearance when not using wxDP_ALLOWNONE. This unfortunately requires hardcoding an arbitrary constant in DoGetBestSize(). Patch used for testing this code for future reference: diff --git a/samples/minimal/minimal.cpp b/samples/minimal/minimal.cpp index 470e765423..a1d5fb9938 100644 --- a/samples/minimal/minimal.cpp +++ b/samples/minimal/minimal.cpp @@ -26,6 +26,8 @@ #include "wx/wx.h" #endif +#include "wx/datectrl.h" + // ---------------------------------------------------------------------------- // resources // ---------------------------------------------------------------------------- @@ -175,6 +177,28 @@ bool MyApp::OnInit() CreateStatusBar(2); SetStatusText("Welcome to wxWidgets!"); #endif // wxUSE_STATUSBAR + + auto sz = new wxBoxSizer(wxVERTICAL); + auto addDPC = [this, sz](int style) + { + sz->Add(new wxDatePickerCtrl(this, wxID_ANY, wxDefaultDateTime, + wxDefaultPosition, wxDefaultSize, style), + wxSizerFlags().Right()); + }; + + addDPC(wxDP_DROPDOWN | wxDP_ALLOWNONE | wxDP_SHOWCENTURY); + addDPC(wxDP_DROPDOWN | wxDP_SHOWCENTURY); + addDPC(wxDP_DROPDOWN | wxDP_ALLOWNONE); + addDPC(wxDP_DROPDOWN); + + sz->AddSpacer(10); + + addDPC(wxDP_SPIN | wxDP_ALLOWNONE | wxDP_SHOWCENTURY); + addDPC(wxDP_SPIN | wxDP_SHOWCENTURY); + addDPC(wxDP_SPIN | wxDP_ALLOWNONE); + addDPC(wxDP_SPIN); + + SetSizerAndFit(sz); } --- src/msw/datetimectrl.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/msw/datetimectrl.cpp b/src/msw/datetimectrl.cpp index ee90f3a476..75225f056a 100644 --- a/src/msw/datetimectrl.cpp +++ b/src/msw/datetimectrl.cpp @@ -207,7 +207,12 @@ wxSize wxDateTimePickerCtrl::DoGetBestSize() const // size when using DTS_SHOWNONE than when not using it, which doesn't make // any sense at all). if ( MSWAllowsNone() ) - size.x += 3 * GetCharWidth(); + { + // The extra 10px here was determined heuristically as the value which + // results in the same layout with and without DTS_SHOWNONE under + // Windows 7 and Windows 10 with 100%, 150% and 200% scaling. + size.x += wxGetSystemMetrics(SM_CXMENUCHECK, m_parent) + 10; + } int scrollY = wxSystemSettings::GetMetric(wxSYS_HSCROLL_ARROW_Y, m_parent); size.y = wxMax(size.y, scrollY); From a24405993415adebb1577731385614a013f3bca8 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sun, 28 Mar 2021 03:13:48 +0200 Subject: [PATCH 5/5] Avoid using DTM_GETIDEALSIZE with DTS_SHOWNONE This message is completely broken when DTS_SHOWNONE is used, it returns wrong result (less than the size without DTS_SHOWNONE) initially and completely wrong results after a DPI change. Create a temporary date time picker control without DTS_SHOWNONE and call DTM_GETIDEALSIZE for it instead. This is wasteful, but at least returns correct results. This commit is best viewed ignoring whitespace-only changes. --- src/msw/datetimectrl.cpp | 83 +++++++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 22 deletions(-) diff --git a/src/msw/datetimectrl.cpp b/src/msw/datetimectrl.cpp index 75225f056a..35ad786034 100644 --- a/src/msw/datetimectrl.cpp +++ b/src/msw/datetimectrl.cpp @@ -164,24 +164,66 @@ wxSize wxDateTimePickerCtrl::DoGetBestSize() const { wxSize size; - // We can ask the control itself to compute its ideal size, but we only use - // it for the horizontal component: the vertical size is not computed - // correctly after the DPI of the window has changed because for every DPI - // change, the returned size is 4 pixels higher, even if the DPI is - // lowered, so we always need to compute it ourselves below. - // - // Also work around https://bugs.winehq.org/show_bug.cgi?id=44680 by - // checking for the return value: even if all "real" MSW systems do support - // this message, Wine does not, even when it's configured to return Vista - // or later version to the application, and returns FALSE for it. - SIZE idealSize; - if ( wxGetWinVersion() >= wxWinVersion_Vista - && ::SendMessage(m_hWnd, DTM_GETIDEALSIZE, 0, (LPARAM)&idealSize) ) + // Use DTM_GETIDEALSIZE to ask the control itself to compute its ideal size. + SIZE idealSize = { 0, 0 }; + if ( wxGetWinVersion() >= wxWinVersion_Vista ) { - size.x = idealSize.cx; - size.y = GetCharHeight(); + // We can't use DTM_GETIDEALSIZE with DTS_SHOWNONE because handling of + // this flag is completely broken (up to at least Window 10 20H2): it's + // not just ignored, but we get completely wrong results when this flag + // is on, e.g. the returned width is less than the width without it or + // much greater than the real value after a DPI change (and growing + // with every new change, even when repeatedly switching between the + // same DPI values, e.g. dragging a window between 2 monitors with + // different scaling). Moreover, note that even without DTS_SHOWNONE, + // DTM_GETIDEALSIZE still returns wrong results for the height after a + // DPI change, so we never use the vertical component of the value + // returned by it. + // + // Unfortunately, resetting this style doesn't work neither, so we have + // to create a whole new window just for this, which is pretty wasteful + // but seems unavoidable. + HWND hwnd; + if ( MSWAllowsNone() ) + { + hwnd = ::CreateWindow + ( + DATETIMEPICK_CLASS, + wxT(""), + ::GetWindowLong(GetHwnd(), GWL_STYLE) & ~DTS_SHOWNONE, + 0, 0, 1, 1, + GetHwndOf(m_parent), + 0, + wxGetInstance(), + NULL + ); + wxCHECK_MSG( hwnd, wxSize(), + wxS("SysDateTimePick32 creation unexpected failed") ); + + wxSetWindowFont(hwnd, GetFont()); + } + else + { + hwnd = GetHwnd(); + } + + // Also work around https://bugs.winehq.org/show_bug.cgi?id=44680 by + // checking for the return value: even if all "real" MSW systems do support + // this message, Wine does not, even when it's configured to return Vista + // or later version to the application, and returns FALSE for it. + if ( ::SendMessage(hwnd, DTM_GETIDEALSIZE, 0, (LPARAM)&idealSize) ) + { + size.x = idealSize.cx; + size.y = GetCharHeight(); + } + + if ( hwnd != GetHwnd() ) + { + ::DestroyWindow(hwnd); + } } - else // Compute the size ourselves. + + if ( !idealSize.cx ) // Compute the size ourselves. { // Use the same native format as the underlying native control. #if wxUSE_INTL @@ -202,16 +244,13 @@ wxSize wxDateTimePickerCtrl::DoGetBestSize() const size.x += wxSystemSettings::GetMetric(wxSYS_HSCROLL_ARROW_X, m_parent); } - // We need to account for the checkbox, if we have one -- DTM_GETIDEALSIZE - // doesn't take it into account (actually, it somehow returns _smaller_ - // size when using DTS_SHOWNONE than when not using it, which doesn't make - // any sense at all). + // Account for the checkbox. if ( MSWAllowsNone() ) { - // The extra 10px here was determined heuristically as the value which + // The extra 8px here was determined heuristically as the value which // results in the same layout with and without DTS_SHOWNONE under // Windows 7 and Windows 10 with 100%, 150% and 200% scaling. - size.x += wxGetSystemMetrics(SM_CXMENUCHECK, m_parent) + 10; + size.x += wxGetSystemMetrics(SM_CXMENUCHECK, m_parent) + 8; } int scrollY = wxSystemSettings::GetMetric(wxSYS_HSCROLL_ARROW_Y, m_parent);