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
// ---------------------------------------------------------------------------