From 137713e0c857d8bc42f5e697a8a97f205090e55b Mon Sep 17 00:00:00 2001 From: Maarten Bent Date: Wed, 5 Oct 2016 21:04:13 +0200 Subject: [PATCH] Add framework for Per-Monitor DPI Awareness on Windows React to the WM_DPICHANGED event and update the size of the child windows and the top-level window. Scale the minimum and maximum window size to the new DPI. Only react to WM_DPICHANGED when DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 is used. --- include/wx/defs.h | 1 + include/wx/msw/missing.h | 6 ++- include/wx/msw/toplevel.h | 11 ++++++ include/wx/msw/window.h | 4 ++ src/msw/toplevel.cpp | 79 +++++++++++++++++++++++++++++++++++++++ src/msw/window.cpp | 41 ++++++++++++++++++++ 6 files changed, 141 insertions(+), 1 deletion(-) diff --git a/include/wx/defs.h b/include/wx/defs.h index 1871ae13e4..f349de2cb2 100644 --- a/include/wx/defs.h +++ b/include/wx/defs.h @@ -2830,6 +2830,7 @@ WX_MSW_DECLARE_HANDLE(HBITMAP); WX_MSW_DECLARE_HANDLE(HIMAGELIST); WX_MSW_DECLARE_HANDLE(HGLOBAL); WX_MSW_DECLARE_HANDLE(HDC); +WX_MSW_DECLARE_HANDLE(DPI_AWARENESS_CONTEXT); typedef WXHINSTANCE WXHMODULE; #undef WX_MSW_DECLARE_HANDLE diff --git a/include/wx/msw/missing.h b/include/wx/msw/missing.h index e56a6bfa68..7f381bb898 100644 --- a/include/wx/msw/missing.h +++ b/include/wx/msw/missing.h @@ -23,7 +23,11 @@ #endif #ifndef WM_PRINTCLIENT - #define WM_PRINTCLIENT 0x318 + #define WM_PRINTCLIENT 0x0318 +#endif + +#ifndef WM_DPICHANGED + #define WM_DPICHANGED 0x02E0 #endif #ifndef DT_HIDEPREFIX diff --git a/include/wx/msw/toplevel.h b/include/wx/msw/toplevel.h index 0256104ebc..5cef6b1e8a 100644 --- a/include/wx/msw/toplevel.h +++ b/include/wx/msw/toplevel.h @@ -165,6 +165,9 @@ protected: int& x, int& y, int& w, int& h) const wxOVERRIDE; + // WM_DPICHANGED handler. + bool HandleDPIChange(const wxSize& newDPI, const wxRect& newRect); + // This field contains the show command to use when showing the window the // next time and also indicates whether the window should be considered // being iconized or maximized (which may be different from whether it's @@ -191,6 +194,14 @@ protected: wxWindowRef m_winLastFocused; private: + // Keep track of the DPI used in this window. So when per-monitor dpi + // awareness is enabled, both old and new DPI are known for + // wxDPIChangedEvent and wxWindow::MSWUpdateOnDPIChange. + wxSize m_activeDPI; + + // This window supports handling per-monitor DPI awareness when the + // application manifest contains PerMonitorV2. + bool m_perMonitorDPIaware; // The system menu: initially NULL but can be set (once) by // MSWGetSystemMenu(). Owned by this window. diff --git a/include/wx/msw/window.h b/include/wx/msw/window.h index 876d0d2b93..318033b2a3 100644 --- a/include/wx/msw/window.h +++ b/include/wx/msw/window.h @@ -586,6 +586,10 @@ public: // Should be overridden by all classes storing the "last focused" window. virtual void WXDoUpdatePendingFocus(wxWindow* WXUNUSED(win)) {} + // Called from WM_DPICHANGED handler for all windows to let them update + // any sizes and fonts used internally when the DPI changes. + void MSWUpdateOnDPIChange(const wxSize& oldDPI, const wxSize& newDPI); + protected: // this allows you to implement standard control borders without // repeating the code in different classes that are not derived from diff --git a/src/msw/toplevel.cpp b/src/msw/toplevel.cpp index 5939b9636f..d2355b0179 100644 --- a/src/msw/toplevel.cpp +++ b/src/msw/toplevel.cpp @@ -104,6 +104,9 @@ void wxTopLevelWindowMSW::Init() m_fsIsShowing = false; m_menuSystem = NULL; + + m_activeDPI = wxDefaultSize; + m_perMonitorDPIaware = false; } WXDWORD wxTopLevelWindowMSW::MSWGetStyle(long style, WXDWORD *exflags) const @@ -246,6 +249,26 @@ WXHWND wxTopLevelWindowMSW::MSWGetParent() const return (WXHWND)hwndParent; } +bool wxTopLevelWindowMSW::HandleDPIChange(const wxSize& newDPI, const wxRect& newRect) +{ + if ( !m_perMonitorDPIaware ) + { + return false; + } + + if ( newDPI != m_activeDPI ) + { + MSWUpdateOnDPIChange(m_activeDPI, newDPI); + m_activeDPI = newDPI; + } + + SetSize(newRect); + + Refresh(); + + return true; +} + WXLRESULT wxTopLevelWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam) { WXLRESULT rc = 0; @@ -306,6 +329,17 @@ WXLRESULT wxTopLevelWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WX #endif // #ifndef __WXUNIVERSAL__ } break; + + case WM_DPICHANGED: + { + const RECT* const prcNewWindow = + reinterpret_cast(lParam); + + processed = HandleDPIChange(wxSize(LOWORD(wParam), + HIWORD(wParam)), + wxRectFromRECT(*prcNewWindow)); + } + break; } if ( !processed ) @@ -314,6 +348,47 @@ WXLRESULT wxTopLevelWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WX return rc; } +namespace +{ + +static bool IsPerMonitorDPIAware(HWND hwnd) +{ + bool dpiAware = false; + + // Determine if 'Per Monitor v2' DPI awareness is enabled in the + // applications manifest. +#if wxUSE_DYNLIB_CLASS + #define WXDPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((WXDPI_AWARENESS_CONTEXT)-4) + typedef WXDPI_AWARENESS_CONTEXT(WINAPI * GetWindowDpiAwarenessContext_t)(HWND hwnd); + typedef BOOL(WINAPI * AreDpiAwarenessContextsEqual_t)(WXDPI_AWARENESS_CONTEXT dpiContextA, WXDPI_AWARENESS_CONTEXT dpiContextB); + static GetWindowDpiAwarenessContext_t s_pfnGetWindowDpiAwarenessContext = NULL; + static AreDpiAwarenessContextsEqual_t s_pfnAreDpiAwarenessContextsEqual = NULL; + static bool s_initDone = false; + + if ( !s_initDone ) + { + wxLoadedDLL dllUser32("user32.dll"); + wxDL_INIT_FUNC(s_pfn, GetWindowDpiAwarenessContext, dllUser32); + wxDL_INIT_FUNC(s_pfn, AreDpiAwarenessContextsEqual, dllUser32); + s_initDone = true; + } + + if ( s_pfnGetWindowDpiAwarenessContext && s_pfnAreDpiAwarenessContextsEqual ) + { + WXDPI_AWARENESS_CONTEXT dpiAwarenessContext = s_pfnGetWindowDpiAwarenessContext(hwnd); + + if ( s_pfnAreDpiAwarenessContextsEqual(dpiAwarenessContext, WXDPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) == TRUE ) + { + dpiAware = true; + } + } +#endif // wxUSE_DYNLIB_CLASS + + return dpiAware; +} + +} + bool wxTopLevelWindowMSW::CreateDialog(const void *dlgTemplate, const wxString& title, const wxPoint& pos, @@ -484,6 +559,10 @@ bool wxTopLevelWindowMSW::Create(wxWindow *parent, EnableCloseButton(false); } + m_activeDPI = GetDPI(); + + m_perMonitorDPIaware = IsPerMonitorDPIAware(GetHwnd()); + // for standard dialogs the dialog manager generates WM_CHANGEUISTATE // itself but for custom windows we have to do it ourselves in order to // make the keyboard indicators (such as underlines for accelerators and diff --git a/src/msw/window.cpp b/src/msw/window.cpp index 77a883460b..f64a7d30a1 100644 --- a/src/msw/window.cpp +++ b/src/msw/window.cpp @@ -4837,6 +4837,47 @@ wxSize wxWindowMSW::GetDPI() const return dpi; } +// Helper function to update the given coordinate by the scaling factor if it +// is set, i.e. different from wxDefaultCoord. +static void ScaleCoordIfSet(int& coord, float scaleFactor) +{ + if ( coord != wxDefaultCoord ) + { + const float coordScaled = coord * scaleFactor; + coord = scaleFactor > 1.0 ? ceil(coordScaled) : floor(coordScaled); + } +} + +void +wxWindowMSW::MSWUpdateOnDPIChange(const wxSize& oldDPI, const wxSize& newDPI) +{ + // update min and max size if necessary + const float scaleFactor = (float)newDPI.y / oldDPI.y; + + ScaleCoordIfSet(m_minHeight, scaleFactor); + ScaleCoordIfSet(m_minWidth, scaleFactor); + ScaleCoordIfSet(m_maxHeight, scaleFactor); + ScaleCoordIfSet(m_maxWidth, scaleFactor); + + InvalidateBestSize(); + + // update children + wxWindowList::compatibility_iterator current = GetChildren().GetFirst(); + while ( current ) + { + wxWindow *childWin = current->GetData(); + // Update all children, except other top-level windows. + // These could be on a different monitor and will get their own + // dpi-changed event. + if ( childWin && !childWin->IsTopLevel() ) + { + childWin->MSWUpdateOnDPIChange(oldDPI, newDPI); + } + + current = current->GetNext(); + } +} + // --------------------------------------------------------------------------- // colours and palettes // ---------------------------------------------------------------------------