You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WinAPI跨设备缩放问题:如何实现窗口与控件比例一致?

解决WinAPI非DPI感知程序控件与窗口比例不一致的问题

首先得说,你遇到的这个问题,本质上是Windows的DPI虚拟化和你手动计算字体尺寸之间的不匹配导致的。非DPI感知程序在高DPI系统上,Windows会自动把整个窗口缩放成“兼容”的大小,但这个过程和你用MulDiv计算字体的逻辑有时候会打架,尤其是不同设备的DPI缩放策略可能有差异(比如有些戴尔设备可能默认开启了特殊的缩放模式)。

下面是一步步的解决方法,帮你实现跨设备比例一致的窗口:

1. 先明确DPI感知的正确姿势

虽然你说程序是非DPI感知,但完全依赖系统虚拟化并不是最优解。建议你把程序设置为系统DPI感知(Windows 10+支持),这样可以让程序自己处理DPI缩放,而不是让系统强制拉伸。

有两种方式设置系统DPI感知:

  • 在程序的manifest文件里添加:
    <application xmlns="urn:schemas-microsoft-com:asm.v3">
      <windowsSettings>
        <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
      </windowsSettings>
    </application>
    
    (true/PM表示系统DPI感知,程序会在每个显示器的DPI下正确缩放)
  • 或者在WinMain开头调用:
    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
    

2. 统一控件尺寸的计算逻辑

你现在的控件是用固定像素值(10,10,500,30)创建的,但这些值没有考虑DPI缩放。正确的做法是把所有的位置、尺寸都从逻辑单位转换为像素单位,或者基于字体的实际大小来计算。

比如,先获取当前窗口的DPI值,然后用MulDiv把逻辑单位转成像素:

// 获取当前窗口的DPI
HDC hdc = GetDC(mWindow);
int dpiY = GetDeviceCaps(hdc, LOGPIXELSY);
ReleaseDC(mWindow, hdc);

// 逻辑单位转像素:以96(默认100%缩放DPI)为基准
int x = MulDiv(10, dpiY, 96);
int y = MulDiv(10, dpiY, 96);
int width = MulDiv(200, dpiY, 96);
int height = MulDiv(30, dpiY, 96);

用这些转换后的像素值创建控件,控件大小会和DPI成正比,不会出现比例失调。

3. 字体创建与控件尺寸的匹配

你用NONCLIENTMETRICS创建字体的思路没问题,但要确保字体高度是基于当前窗口的DPI计算的。另外,创建控件后最好用GetTextMetrics获取字体的实际高度,再调整控件高度,保证文本能完整显示:

// 创建字体后,获取字体的实际尺寸
HDC hdcCtrl = GetDC(someText);
SelectObject(hdcCtrl, normalFont);
TEXTMETRIC tm;
GetTextMetrics(hdcCtrl, &tm);
ReleaseDC(someText, hdcCtrl);

// 调整控件高度为字体高度加上上下边距
SetWindowPos(someText, NULL, x, y, width, tm.tmHeight + 8, SWP_NOZORDER);

4. 处理WM_DPICHANGED消息

当用户改变显示器的DPI缩放比例时,系统会给窗口发送WM_DPICHANGED消息,你需要在窗口过程里处理这个消息,重新调整控件的位置、尺寸和字体:

case WM_DPICHANGED: {
    UINT newDpi = HIWORD(wParam);
    RECT* newRect = (RECT*)lParam;
    
    // 调整窗口大小以匹配新DPI
    SetWindowPos(hwnd, NULL, newRect->left, newRect->top, 
                 newRect->right - newRect->left, newRect->bottom - newRect->top, 
                 SWP_NOZORDER | SWP_NOACTIVATE);
    
    // 重新计算字体
    NONCLIENTMETRICS ncm;
    ncm.cbSize = sizeof(NONCLIENTMETRICS);
    SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0);
    ncm.lfMessageFont.lfWidth = 0;
    ncm.lfMessageFont.lfHeight = -MulDiv(16, newDpi, 72);
    HFONT newFont = CreateFontIndirect(&ncm.lfMessageFont);
    
    // 更新控件字体
    SendMessage(someText, WM_SETFONT, (WPARAM)newFont, MAKELPARAM(TRUE, 0));
    DeleteObject(normalFont);
    normalFont = newFont;
    
    // 重新计算控件位置和尺寸
    int x = MulDiv(10, newDpi, 96);
    int y = MulDiv(10, newDpi, 96);
    int width = MulDiv(200, newDpi, 96);
    // 基于新字体调整高度
    HDC hdcCtrl = GetDC(someText);
    SelectObject(hdcCtrl, newFont);
    TEXTMETRIC tm;
    GetTextMetrics(hdcCtrl, &tm);
    ReleaseDC(someText, hdcCtrl);
    SetWindowPos(someText, NULL, x, y, width, tm.tmHeight + 8, SWP_NOZORDER);
    
    break;
}

修正后的示例代码关键部分

把上面的逻辑整合到你的代码里,修改后的核心片段大概是这样:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    // 设置系统DPI感知
    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
    
    // ... 注册窗口类的代码不变 ...
    
    // 创建窗口的代码不变 ...
    
    HDC hdc = GetDC(mWindow);
    int dpiY = GetDeviceCaps(hdc, LOGPIXELSY);
    
    NONCLIENTMETRICS ncm;
    ncm.cbSize = sizeof(NONCLIENTMETRICS);
    SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0);
    ncm.lfMessageFont.lfWidth = 0;
    ncm.lfMessageFont.lfHeight = -MulDiv(16, dpiY, 72);
    HFONT normalFont = CreateFontIndirect(&ncm.lfMessageFont);
    
    // 计算控件的像素位置和尺寸(基于逻辑单位转DPI)
    int x = MulDiv(10, dpiY, 96);
    int y = MulDiv(10, dpiY, 96);
    int width = MulDiv(200, dpiY, 96);
    
    HWND someText = CreateWindow(
        "Static",
        "This text will scale correctly now",
        WS_VISIBLE | WS_CHILD | SS_LEFT,
        x, y, width, 1, // 先设临时高度,后续调整
        mWindow, (HMENU) NULL, hInstance, NULL);
    SendMessage(someText, WM_SETFONT, (WPARAM) normalFont, MAKELPARAM(TRUE, 0));
    
    // 调整控件高度以匹配字体
    HDC hdcCtrl = GetDC(someText);
    SelectObject(hdcCtrl, normalFont);
    TEXTMETRIC tm;
    GetTextMetrics(hdcCtrl, &tm);
    ReleaseDC(someText, hdcCtrl);
    SetWindowPos(someText, NULL, x, y, width, tm.tmHeight + 8, SWP_NOZORDER);
    
    // ... 消息循环的代码不变 ...
}

为什么之前的代码在部分电脑出问题?

因为非DPI感知程序的虚拟化缩放是系统级的,不同设备的系统设置可能不同(比如有些设备开启了“修复高DPI缩放问题”的兼容性选项),导致系统的缩放比例和你用MulDiv计算的字体比例不一致,最终文本显示过大或控件错位。而让程序自己处理DPI感知,就能避开系统虚拟化的不确定性。

内容的提问来源于stack exchange,提问作者Malanche

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:39:41