Win10RS1/2/3的DPI支持改进简介
Win10RS1(1607/周年更新):新增一套API,使得每个窗口可以具有不同的DPI缩放兼容性,并大大简化了多显示器DPI编程。WPF可选支持多显示器DPI编程。
Win10RS2(1703/创意者更新):新增PerMonitorV2模式,简化Win32和WinForms的多显示器DPI编程。新增gdiScaling选项,改善DPI虚拟化画质。程序兼容型选项卡新增DPI兼容性设置,允许用户强行打开DPI虚拟化。
Win10RS3(秋季创意者更新,待发布):更改系统级DPI不再需要注销并重新登录,只需重新打开未响应DPI变化的程序即可。
WPF编程
.NET 4.6.2已经支持Win10 RS1的多显示器DPI支持模式,不过,为了使用这个功能,你还需要作以下操作:
1、声明程序支持Win10 RS1的多显示器DPI支持模式,但不支持Win8.1的多显示器DPI支持模式
编写App.manifest,在assembly标签下添加如下声明:
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowssettings>
<dpiawareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiawareness>
<dpiaware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiaware>
</windowssettings>
</application>
2、设置目标框架为4.6.2以上,或者在App.config的configuration标签下添加如下声明:
<runtime>
<appcontextswitchoverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false">
</appcontextswitchoverrides></runtime>
Windows Forms编程
Windows Forms已经在.NET Framework 4.7支持Win10 RS2的多显示器DPI第二版。不过,由于Windows Forms的架构限制,实际上有一些bug,比如在频繁调整下,ToolStrip控件会越来越大。
**编写支持多显示器DPI的应用程序,建议使用WPF。**因为WPF基于浮点坐标和矢量绘图,能够更好地支持DPI的动态变化(Windows Forms基于整数坐标和像素绘图)。
如果你想尝试一下这个新功能,可以按照下面的方法声明。
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
首先在App.manifest中允许.NET检测Win10系统版本
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedos id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}">
</supportedos></application>
</compatibility>
然后在App.config中指定目标版本为4.7,并开启Windows Forms的多显示器DPI支持模式第二版
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedruntime version="v4.0" sku=".NETFramework,Version=v4.7">
</supportedruntime></startup>
<system.windows.forms.applicationconfigurationsection>
<add key="DpiAwareness" value="PerMonitorV2">
</add></system.windows.forms.applicationconfigurationsection>
</configuration>
GDI缩放技术
Win10 RS2支持GDI缩放技术,可以在大多数情况下取代原有的像素缩放技术,优化一些内容的显示。
App.manifest声明方法:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestversion="1.0">
<assemblyidentity type="win32" name="MyApp" version="1.0.0.0">
<asmv3:application>
<asmv3:windowssettings>
<dpiaware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">false</dpiaware>
<gdiscaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiscaling>
</asmv3:windowssettings>
</asmv3:application>
</assemblyidentity></assembly>
Win32编程
Win10RS1添加了一些动态DPI函数,这些函数比Win8.1的那些使用起来更容易。
Win10RS2添加了多显示器DPI支持模式第二版,增加了对话框、菜单栏、非客户区的自动缩放支持,以及子控件的DPI消息通知。
经过实测,其它通用控件可以近乎完美缩放,但是工具栏图标不能自动缩放,还是需要处理一下WM_DPICHANGED,然后重新给工具栏指定一组更大的位图。
不一定要支持Windows 8.1/10TH1/10TH2的多显示器DPI支持模式,甚至连10RS1的都不一定要支持,因为多显示器动态DPI的API是逐渐完善的,而且需要使用多显示器DPI支持的用户一般倾向于升级到最新版Windows 10。
另外要注意,SetProcessDpiAwarenessContext函数,虽然MSDN上写的Win10RS2,实际上在Win10RS1已经导出了,所以如果以-4调用,一定要检查是否返回FALSE,返回FALSE的话需要再以-2调用一次。
App.mainfest多显示器DPI支持模式第二版声明方法:
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowssettings>
<dpiawareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,System</dpiawareness>
<dpiaware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiaware>
</windowssettings>
</application>
这里的声明取自Windows SDK 10.0.15063.0头文件,而非MSDN Library开发者文档。(2017/4/6更新)
#pragma once
#include <windows.h>
#include <uxtheme.h>
#define DefineDLLEntry(name) T_##name *name
#define LoadDLLEntry(hmod, name) name = (T_##name *)GetProcAddress(hmod, #name)
#define _Inout_
#define _In_
#define _Out_
#define _In_opt_
// Windows 7加入的窗口消息
#define WM_DPICHANGED 0x02E0
// Windows 10 RS2加入的窗口消息
#define WM_DPICHANGED_BEFOREPARENT 0x02E2
#define WM_DPICHANGED_AFTERPARENT 0x02E3
#define WM_GETDPISCALEDSIZE 0x02E4
#if NTDDI_VERSION < 0x0A000002
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
typedef enum DPI_AWARENESS {
DPI_AWARENESS_INVALID = -1,
DPI_AWARENESS_UNAWARE = 0,
DPI_AWARENESS_SYSTEM_AWARE = 1,
DPI_AWARENESS_PER_MONITOR_AWARE = 2
} DPI_AWARENESS;
#endif
#if NTDDI_VERSION < 0x0A000003
typedef enum DIALOG_DPI_CHANGE_BEHAVIORS {
DDC_DEFAULT = 0x0000,
DDC_DISABLE_ALL = 0x0001,
DDC_DISABLE_RESIZE = 0x0002,
DDC_DISABLE_CONTROL_RELAYOUT = 0x0004,
} DIALOG_DPI_CHANGE_BEHAVIORS;
typedef enum DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS {
DCDC_DEFAULT = 0x0000,
DCDC_DISABLE_FONT_UPDATE = 0x0001,
DCDC_DISABLE_RELAYOUT = 0x0002,
} DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS;
#endif
// 不支持DPI
#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
// 系统级DPI(传统DPI模式)
#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
// 逐显示器DPI第一版(Win8.1/Win10RS1模式)
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
// 逐显示器DPI第二版(Win10RS2模式)
// 支持对话框、菜单、非客户区自动缩放,支持子控件DPI通知
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
// Windows 10 RS1、Windows 10 RS2加入的高DPI支持API
struct Win10DPI
{
typedef BOOL WINAPI T_AdjustWindowRectExForDpi(
_Inout_ LPRECT lpRect,
_In_ DWORD dwStyle,
_In_ BOOL bMenu,
_In_ DWORD dwExStyle,
_In_ UINT dpi
);
typedef BOOL WINAPI T_AreDpiAwarenessContextsEqual(
_In_ DPI_AWARENESS_CONTEXT dpiContextA,
_In_ DPI_AWARENESS_CONTEXT dpiContextB
);
typedef BOOL WINAPI T_EnableNonClientDpiScaling(
_In_ HWND hwnd
);
typedef DPI_AWARENESS WINAPI T_GetAwarenessFromDpiAwarenessContext(
_In_ DPI_AWARENESS_CONTEXT value
);
typedef UINT WINAPI T_GetDpiForSystem(void);
typedef UINT WINAPI T_GetDpiForWindow(
_In_ HWND hwnd
);
typedef int WINAPI T_GetSystemMetricsForDpi(
_In_ int nIndex,
_In_ UINT dpi
);
typedef DPI_AWARENESS_CONTEXT WINAPI T_GetThreadDpiAwarenessContext(void);
typedef DPI_AWARENESS_CONTEXT WINAPI T_GetWindowDpiAwarenessContext(
_In_ HWND hwnd
);
typedef BOOL WINAPI T_IsValidDpiAwarenessContext(
_In_ DPI_AWARENESS_CONTEXT value
);
// Windows 8.1加入,但仍然适用
typedef BOOL WINAPI T_LogicalToPhysicalPointForPerMonitorDPI(
_In_ HWND hwnd,
_Inout_ LPPOINT lpPoint
);
// Windows 8.1加入,但仍然适用
typedef BOOL WINAPI T_PhysicalToLogicalPointForPerMonitorDPI(
_In_ HWND hwnd,
_Inout_ LPPOINT lpPoint
);
typedef DPI_AWARENESS_CONTEXT WINAPI T_SetThreadDpiAwarenessContext(
_In_ DPI_AWARENESS_CONTEXT dpiContext
);
typedef BOOL WINAPI T_SystemParametersInfoForDpi(
_In_ UINT uiAction,
_In_ UINT uiParam,
_Inout_ PVOID pvParam,
_In_ UINT fWinIni,
_In_ UINT dpi
);
// Windows 10 RS2加入
// 这个其实Windows 10 RS1已经导出了
typedef BOOL WINAPI T_SetProcessDpiAwarenessContext(
_In_ DPI_AWARENESS_CONTEXT value
);
typedef BOOL WINAPI T_SetDialogDpiChangeBehavior(
_In_ HWND hDlg,
_In_ DIALOG_DPI_CHANGE_BEHAVIORS mask,
_In_ DIALOG_DPI_CHANGE_BEHAVIORS values
);
typedef DIALOG_DPI_CHANGE_BEHAVIORS WINAPI T_GetDialogDpiChangeBehavior(
_In_ HWND hDlg
);
typedef BOOL WINAPI T_SetDialogControlDpiChangeBehavior(
_In_ HWND hWnd,
_In_ DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS mask,
_In_ DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS values
);
typedef DIALOG_CONTROL_DPI_CHANGE_BEHAVIORS WINAPI T_GetDialogControlDpiChangeBehavior(
_In_ HWND hWnd
);
typedef HTHEME WINAPI T_OpenThemeDataForDpi(
_In_opt_ HWND hwnd,
_In_ LPCWSTR pszClassList,
_In_ UINT dpi
);
DefineDLLEntry(AdjustWindowRectExForDpi);
DefineDLLEntry(AreDpiAwarenessContextsEqual);
DefineDLLEntry(EnableNonClientDpiScaling);
DefineDLLEntry(GetAwarenessFromDpiAwarenessContext);
DefineDLLEntry(GetDpiForSystem);
DefineDLLEntry(GetDpiForWindow);
DefineDLLEntry(GetSystemMetricsForDpi);
DefineDLLEntry(GetThreadDpiAwarenessContext);
DefineDLLEntry(GetWindowDpiAwarenessContext);
DefineDLLEntry(IsValidDpiAwarenessContext);
DefineDLLEntry(LogicalToPhysicalPointForPerMonitorDPI);
DefineDLLEntry(PhysicalToLogicalPointForPerMonitorDPI);
DefineDLLEntry(SetThreadDpiAwarenessContext);
DefineDLLEntry(SystemParametersInfoForDpi);
DefineDLLEntry(SetProcessDpiAwarenessContext);
DefineDLLEntry(SetDialogDpiChangeBehavior);
DefineDLLEntry(GetDialogDpiChangeBehavior);
DefineDLLEntry(SetDialogControlDpiChangeBehavior);
DefineDLLEntry(GetDialogControlDpiChangeBehavior);
Win10DPI()
{
HMODULE huser32 = GetModuleHandleA("user32");
LoadDLLEntry(huser32, AdjustWindowRectExForDpi);
LoadDLLEntry(huser32, AreDpiAwarenessContextsEqual);
LoadDLLEntry(huser32, EnableNonClientDpiScaling);
LoadDLLEntry(huser32, GetAwarenessFromDpiAwarenessContext);
LoadDLLEntry(huser32, GetDpiForSystem);
LoadDLLEntry(huser32, GetDpiForWindow);
LoadDLLEntry(huser32, GetSystemMetricsForDpi);
LoadDLLEntry(huser32, GetThreadDpiAwarenessContext);
LoadDLLEntry(huser32, GetWindowDpiAwarenessContext);
LoadDLLEntry(huser32, IsValidDpiAwarenessContext);
LoadDLLEntry(huser32, LogicalToPhysicalPointForPerMonitorDPI);
LoadDLLEntry(huser32, PhysicalToLogicalPointForPerMonitorDPI);
LoadDLLEntry(huser32, SetThreadDpiAwarenessContext);
LoadDLLEntry(huser32, SystemParametersInfoForDpi);
LoadDLLEntry(huser32, SetProcessDpiAwarenessContext);
LoadDLLEntry(huser32, SetDialogDpiChangeBehavior);
LoadDLLEntry(huser32, GetDialogDpiChangeBehavior);
LoadDLLEntry(huser32, SetDialogControlDpiChangeBehavior);
LoadDLLEntry(huser32, GetDialogControlDpiChangeBehavior);
}
};
struct WinVistaDPI
{
typedef BOOL WINAPI T_SetProcessDPIAware(VOID);
typedef BOOL WINAPI T_IsProcessDPIAware(VOID);
typedef BOOL WINAPI T_LogicalToPhysicalPoint(
_In_ HWND hWnd,
_Inout_ LPPOINT lpPoint
);
typedef BOOL WINAPI T_PhysicalToLogicalPoint(
_In_ HWND hWnd,
_Inout_ LPPOINT lpPoint
);
DefineDLLEntry(SetProcessDPIAware);
DefineDLLEntry(IsProcessDPIAware);
DefineDLLEntry(LogicalToPhysicalPoint);
DefineDLLEntry(PhysicalToLogicalPoint);
WinVistaDPI()
{
HMODULE huser32 = GetModuleHandleA("user32");
LoadDLLEntry(huser32, SetProcessDPIAware);
LoadDLLEntry(huser32, IsProcessDPIAware);
LoadDLLEntry(huser32, LogicalToPhysicalPoint);
LoadDLLEntry(huser32, PhysicalToLogicalPoint);
}
};
// Win8.1加入,但已不适用的API
#ifdef WIN81DPI_DEPRECATED
typedef enum _MONITOR_DPI_TYPE {
MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1,
MDT_RAW_DPI = 2,
MDT_DEFAULT = MDT_EFFECTIVE_DPI
} Monitor_DPI_Type;
typedef enum _PROCESS_DPI_AWARENESS {
PROCESS_DPI_UNAWARE = 0,
PROCESS_SYSTEM_DPI_AWARE = 1,
PROCESS_PER_MONITOR_DPI_AWARE = 2
} PROCESS_DPI_AWARENESS;
typedef enum _SHELL_UI_COMPONENT {
SHELL_UI_COMPONENT_TASKBARS = 0,
SHELL_UI_COMPONENT_NOTIFICATIONAREA = 1,
SHELL_UI_COMPONENT_DESKBAND = 2
} SHELL_UI_COMPONENT;
typedef HRESULT WINAPI T_GetDpiForMonitor(
_In_ HMONITOR hmonitor,
_In_ MONITOR_DPI_TYPE dpiType,
_Out_ UINT *dpiX,
_Out_ UINT *dpiY
);
typedef UINT WINAPI T_GetDpiForShellUiComponent(
_In_ SHELL_UI_COMPONENT component
);
typedef HRESULT WINAPI T_GetProcessDpiAwareness(
_In_ HANDLE hprocess,
_Out_ PROCESS_DPI_AWARENESS *value
);
typedef HRESULT WINAPI T_SetProcessDpiAwareness(
_In_ PROCESS_DPI_AWARENESS value
);
#endif
</uxtheme.h></windows.h>
(未完待续)