【原创】实现每显示器高DPI识别(Per-Monitor DPI Aware)的注意事项
本帖最后由 Mouri_Naruto 于 2016-7-6 11:09 编辑为了在Win10下随时随地程序窗口都不模糊都能正常缩放,你必须在你的程序中加入Per-Monitor DPI Aware支持
因为Win10开始,用户在设置应用中调整DPI是不需要注销的!于是一些System Aware和Unaware的应用会被DWM虚拟化技术进行粗暴缩放,于是就模糊了
如果你定义你的程序支持Per-Monitor DPI Aware,但又没实现Per-Monitor DPI Aware支持的话,就会无法动态缩放(体验比模糊更加糟糕)
实现Per-Monitor DPI Aware支持,首先在程序清单中加入<dpiAware>True/PM</dpiAware>;或者你也可以在VS的项目属性里面设置即可
有人绝对会问,为何不调用API设置,因为对应API要求Windows 8.1以上;所以为了方便起见,我建议还是在清单文件添加
接着,获取DPI的部分需要重写
因为我们惯用的GetDeviceCaps只能获取系统DPI(MSDN指明是与显卡驱动的实现有关)
要获取真实DPI,你需要使用GetDpiForMonitor;但这个API至少要8.1起步
我在下面提供一些参考
重写的GetDpiForMonitor
#include <ShellScalingApi.h>
static HRESULT WINAPI GetDpiForMonitor(
_In_ HMONITOR hmonitor,
_In_ MONITOR_DPI_TYPE dpiType,
_Out_ UINT *dpiX,
_Out_ UINT *dpiY)
{
HINSTANCE hInstWinSta = LoadLibraryW(L"SHCore.dll");
if (hInstWinSta == nullptr) return E_NOINTERFACE;
typedef HRESULT(WINAPI * PFN_GDFM)(
HMONITOR, MONITOR_DPI_TYPE, UINT*, UINT*);
PFN_GDFM pGetDpiForMonitor =
(PFN_GDFM)GetProcAddress(hInstWinSta, "GetDpiForMonitor");
if (pGetDpiForMonitor == nullptr) return E_NOINTERFACE;
return pGetDpiForMonitor(hmonitor, dpiType, dpiX, dpiY);
}
获取DPI比例的代码逻辑片段
// 获取DPI比例
HRESULT hr = E_FAIL;
hr = GetDpiForMonitor(
MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST),
MDT_EFFECTIVE_DPI, (UINT*)&g_xDPI, (UINT*)&g_yDPI);
if (hr != S_OK)
{
g_xDPI = GetDeviceCaps(GetDC(hWnd), LOGPIXELSX);
g_yDPI = GetDeviceCaps(GetDC(hWnd), LOGPIXELSY);
}
然后实现窗口的WM_DPICHANGED消息,该消息有用的参数
// LOWORD(wParam); // x轴DPI
// HIWORD(wParam); // y轴DPI
// (RECT*)lParam /// 系统建议的新窗口位置
你应该在该消息实现对界面的调整或者通知你的UI框架
到这里,MSDN文档的Sample基本就结束了
但是你要真的碰到不同DPI缩放的显示器或者调整DPI后没有注销,然后你就傻了
因为标题栏和菜单不支持缩放
要实现这个问题,貌似只有一种方式,那就是隐藏非客户区,自己绘制一个标题栏并且自己绘制菜单
但是如果你不要自绘标题栏和菜单(太麻烦了,而且浪费时间)
你可以学习Windows 10的命令提示符窗口,调用(未公开)API让系统自动帮你缩放标题栏和菜单
// 开启对话框Per-Monitor DPI Aware支持(至少Win10)
inline BOOL EnablePerMonitorDialogScaling()
{
typedef BOOL(WINAPI *PFN_EnablePerMonitorDialogScaling)();
PFN_EnablePerMonitorDialogScaling pEnablePerMonitorDialogScaling =
(PFN_EnablePerMonitorDialogScaling)GetProcAddress(
GetModuleHandleW(L"user32.dll"), (LPCSTR)2577);
if (pEnablePerMonitorDialogScaling) return pEnablePerMonitorDialogScaling();
return -1;
}
// 开启子窗体DPI消息(至少Win10)
inline BOOL EnableChildWindowDpiMessage(
_In_ HWND hWnd,
_In_ BOOL bEnable)
{
typedef BOOL(WINAPI *PFN_EnableChildWindowDpiMessage)(HWND, BOOL);
PFN_EnableChildWindowDpiMessage pEnableChildWindowDpiMessage =
(PFN_EnableChildWindowDpiMessage)GetProcAddress(
GetModuleHandleW(L"user32.dll"), "EnableChildWindowDpiMessage");
if (pEnableChildWindowDpiMessage)
return pEnableChildWindowDpiMessage(hWnd, bEnable);
return -1;
}
以上API定义通过IDA查看Win10的ConhostV2.dll得到
可用于Windows 10 Threshold 1及以后(如果微软不去掉的话)
在显示对话框前调用下EnablePerMonitorDialogScaling();不用实现WM_DPICHANGED消息,系统会自动帮你缩放对话框
窗体应用,需要用CreateWindowEx创建窗体后;调用EnableChildWindowDpiMessage(窗口hWnd,TRUE);
然后适当在WM_DPICHANGED添加调整代码即可完美Per-Monitor DPI Aware
效果图,仅供参考(调整DPI后没有注销,对话框应用)
希望我的资料能够对开发应用的人有帮助
毛利, 2016/7/6
楼主厉害啊,看看, 厉害啊,学习了 看的有点晕 我先去静一下 好深奥呀,楼主能细讲下吗 *﹏暎雪ヤ 发表于 2016-7-24 19:40
好深奥呀,楼主能细讲下吗
我想知道你哪些地方不明白,这样我可以更好的帮助你,谢谢 从远景过来支持下楼主的 NSudo 4.0,同时也支持下 Per-Monitor DPI Aware 的实现代码 多谢楼主分享,刚好遇到HIGH DPI问题。 留名备用。
页:
[1]