Mouri_Naruto 发表于 2016-7-6 11:06

【原创】实现每显示器高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











ZMZwise 发表于 2016-7-6 11:21

楼主厉害啊,看看,

王美君 发表于 2016-7-6 11:49

厉害啊,学习了

云在青霄水在瓶 发表于 2016-7-6 21:48

看的有点晕         我先去静一下

*﹏暎雪ヤ 发表于 2016-7-24 19:40

好深奥呀,楼主能细讲下吗

Mouri_Naruto 发表于 2016-7-24 19:56

*﹏暎雪ヤ 发表于 2016-7-24 19:40
好深奥呀,楼主能细讲下吗

我想知道你哪些地方不明白,这样我可以更好的帮助你,谢谢

不懂破解 发表于 2016-7-27 11:41

从远景过来支持下楼主的 NSudo 4.0,同时也支持下 Per-Monitor DPI Aware 的实现代码

王山而 发表于 2017-8-8 08:08

多谢楼主分享,刚好遇到HIGH DPI问题。

模天 发表于 2017-9-6 19:07

留名备用。
页: [1]
查看完整版本: 【原创】实现每显示器高DPI识别(Per-Monitor DPI Aware)的注意事项