Win32/GDI编程中的DPI
打开高DPI的方法
与.NET的方法相同,所不同的是Win32编程中不需要手动引入GetModuleHandle和GetProcAddress两个WinAPI。
<code class="language-cpp">HMODULE hUser32 = GetModuleHandleA("user32"); if (hUser32) { FARPROC pSetProcessDpiAware = GetProcAddress(hUser32, "SetProcessDPIAware"); if (pSetProcessDpiAware) { pSetProcessDpiAware(); } } </code>
获取系统DPI的方法
和.NET不同的是,Win32并不会给我们自动缩放,大部分情况下我们需要手动缩放。这时就需要
首先通过GetDC(NULL)获取屏幕的HDC,然后GetDeviceCaps(hscreendc, LOGPIXELSX)获取X方向的DPI,使用GetDeviceCaps(hscreendc, LOGPIXELSY)获取Y方向的DPI,一般来说,它们是相等的,获取完毕,务必使用ReleaseDC(NULL, hscreendc)释放屏幕的HDC。
<code class="language-cpp">int dpix = 96; int dpiy = 96; HDC hscreendc = GetDC(NULL); if (hscreendc) { dpix = GetDeviceCaps(hscreendc, LOGPIXELSX); dpiy = GetDeviceCaps(hscreendc, LOGPIXELSY); ReleaseDC(NULL, hscreendc); } </code>
使用MulDiv进行手工缩放
一般来说,我们可以使用MulDiv进行缩放:
<code class="language-cpp">RECT rc = { 0, 0, 320, 240 }; rc.left = MulDiv(rc.left, dpix, 96); rc.right = MulDiv(rc.right, dpix, 96); rc.top = MulDiv(rc.top, dpiy, 96); rc.bottom = MulDiv(rc.bottom, dpiy, 96); </code>
MulDiv只适用于整数,对于浮点数来说,还是要使用x * dpix / 96.0f和y * dpiy / 96.0f。
使用AdjustWindowRect调整窗口大小
CreateWindow要求窗口大小,但是我们通常期望设置客户区大小,这时候就要使用AdjustWindowRect了。注意AdjustWindowRect不能预知菜单栏有没有被折行,因此如果菜单项很多的话,不一定能计算出准确的窗口大小。
以下是一个经过DPI缩放和AdjustWindowRect计算的后调用CreateWindows的程序:
<code class="language-cpp">AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, TRUE); hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hInstance, NULL); </code>
关于GDI+
与WinForms第二种方法基本相同,不同的是,Win32版本的GDI+没有DrawImageUnscaled,需要使用g.DrawImage(&bmp, 0, 0, XXXXXXtWidth(), XXXXXXtHeight());这种用法。
这里有个坑,获得HDC之后必须第一时间创建Graphics对象,不能GDI和GDI+混用,不然GetLastStatus()会返回3,即OutOfMemory,无法使用GetDpiX()和GetDpiY()函数。
关于GDI
由于GDI年代比较久远,不提供浮点数计算,因此不能想当然的使用MM_LOENGLISH或MM_HIENGLISH。比较科学的方法是使用MM_TWIPS。它与DIP之间的换算关系如下:
TWIPS.x = DIP.x * 20
TWIPS.y = DIP.y * -20
由于GDI对除MM_TEXT以外的单位都使用Y轴向上的坐标系,因此比较蛋疼的是,y需要取负才能表示显示在屏幕上的点。
<code class="language-cpp">case WM_PAINT: { HDC hdc = BeginPaint(hWnd, &ps); SetMapMode(hdc, MM_TWIPS); MoveToEx(hdc, 10 * 20, 10 * -20, NULL); LineTo(hdc, 100 * 20, 100 * -20); EndPaint(hWnd, &ps); return 0; } </code>
此外,TWIPS与Pixel的转换不需要手工进行,只需要使用LPtoDP和DPtoLP即可。
<code class="language-cpp">RECT rc = { 10 * 20, 10 * -20, 300 * 20, 200 * -20 }; LPtoDP(hdc, (LPPOINT)&rc, 2); </code>
运行效果:
200字以内,仅用于支线交流,主线讨论请采用回复功能。