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.
This commit is contained in:
Maarten Bent
2016-10-05 21:04:13 +02:00
parent 70f8bf4c86
commit 137713e0c8
6 changed files with 141 additions and 1 deletions

View File

@@ -2830,6 +2830,7 @@ WX_MSW_DECLARE_HANDLE(HBITMAP);
WX_MSW_DECLARE_HANDLE(HIMAGELIST); WX_MSW_DECLARE_HANDLE(HIMAGELIST);
WX_MSW_DECLARE_HANDLE(HGLOBAL); WX_MSW_DECLARE_HANDLE(HGLOBAL);
WX_MSW_DECLARE_HANDLE(HDC); WX_MSW_DECLARE_HANDLE(HDC);
WX_MSW_DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
typedef WXHINSTANCE WXHMODULE; typedef WXHINSTANCE WXHMODULE;
#undef WX_MSW_DECLARE_HANDLE #undef WX_MSW_DECLARE_HANDLE

View File

@@ -23,7 +23,11 @@
#endif #endif
#ifndef WM_PRINTCLIENT #ifndef WM_PRINTCLIENT
#define WM_PRINTCLIENT 0x318 #define WM_PRINTCLIENT 0x0318
#endif
#ifndef WM_DPICHANGED
#define WM_DPICHANGED 0x02E0
#endif #endif
#ifndef DT_HIDEPREFIX #ifndef DT_HIDEPREFIX

View File

@@ -165,6 +165,9 @@ protected:
int& x, int& y, int& x, int& y,
int& w, int& h) const wxOVERRIDE; 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 // This field contains the show command to use when showing the window the
// next time and also indicates whether the window should be considered // next time and also indicates whether the window should be considered
// being iconized or maximized (which may be different from whether it's // being iconized or maximized (which may be different from whether it's
@@ -191,6 +194,14 @@ protected:
wxWindowRef m_winLastFocused; wxWindowRef m_winLastFocused;
private: 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 <dpiAwareness>PerMonitorV2</dpiAwareness>.
bool m_perMonitorDPIaware;
// The system menu: initially NULL but can be set (once) by // The system menu: initially NULL but can be set (once) by
// MSWGetSystemMenu(). Owned by this window. // MSWGetSystemMenu(). Owned by this window.

View File

@@ -586,6 +586,10 @@ public:
// Should be overridden by all classes storing the "last focused" window. // Should be overridden by all classes storing the "last focused" window.
virtual void WXDoUpdatePendingFocus(wxWindow* WXUNUSED(win)) {} 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: protected:
// this allows you to implement standard control borders without // this allows you to implement standard control borders without
// repeating the code in different classes that are not derived from // repeating the code in different classes that are not derived from

View File

@@ -104,6 +104,9 @@ void wxTopLevelWindowMSW::Init()
m_fsIsShowing = false; m_fsIsShowing = false;
m_menuSystem = NULL; m_menuSystem = NULL;
m_activeDPI = wxDefaultSize;
m_perMonitorDPIaware = false;
} }
WXDWORD wxTopLevelWindowMSW::MSWGetStyle(long style, WXDWORD *exflags) const WXDWORD wxTopLevelWindowMSW::MSWGetStyle(long style, WXDWORD *exflags) const
@@ -246,6 +249,26 @@ WXHWND wxTopLevelWindowMSW::MSWGetParent() const
return (WXHWND)hwndParent; 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 wxTopLevelWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam)
{ {
WXLRESULT rc = 0; WXLRESULT rc = 0;
@@ -306,6 +329,17 @@ WXLRESULT wxTopLevelWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WX
#endif // #ifndef __WXUNIVERSAL__ #endif // #ifndef __WXUNIVERSAL__
} }
break; break;
case WM_DPICHANGED:
{
const RECT* const prcNewWindow =
reinterpret_cast<const RECT*>(lParam);
processed = HandleDPIChange(wxSize(LOWORD(wParam),
HIWORD(wParam)),
wxRectFromRECT(*prcNewWindow));
}
break;
} }
if ( !processed ) if ( !processed )
@@ -314,6 +348,47 @@ WXLRESULT wxTopLevelWindowMSW::MSWWindowProc(WXUINT message, WXWPARAM wParam, WX
return rc; 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, bool wxTopLevelWindowMSW::CreateDialog(const void *dlgTemplate,
const wxString& title, const wxString& title,
const wxPoint& pos, const wxPoint& pos,
@@ -484,6 +559,10 @@ bool wxTopLevelWindowMSW::Create(wxWindow *parent,
EnableCloseButton(false); EnableCloseButton(false);
} }
m_activeDPI = GetDPI();
m_perMonitorDPIaware = IsPerMonitorDPIAware(GetHwnd());
// for standard dialogs the dialog manager generates WM_CHANGEUISTATE // for standard dialogs the dialog manager generates WM_CHANGEUISTATE
// itself but for custom windows we have to do it ourselves in order to // itself but for custom windows we have to do it ourselves in order to
// make the keyboard indicators (such as underlines for accelerators and // make the keyboard indicators (such as underlines for accelerators and

View File

@@ -4837,6 +4837,47 @@ wxSize wxWindowMSW::GetDPI() const
return dpi; 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 // colours and palettes
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------