如何在 Qt 4.8 for Windows 中获取设备像素比?

Posted

技术标签:

【中文标题】如何在 Qt 4.8 for Windows 中获取设备像素比?【英文标题】:How to get device pixel ratio in Qt 4.8 for Windows? 【发布时间】:2016-01-06 12:23:38 【问题描述】:

我们有使用 Qt 4.8 的桌面应用程序。 我们正在尝试支持不同的 DPI 屏幕,例如 Mac Retina、Surface Pro 4 设备。 对于 Mac,我们可以通过单个函数调用获得设备像素比:

CGFloat devicePixelRatio = [[NSScreen mainScreen] backingScaleFactor];

WinAPI 中是否有任何实用函数可用于获取设备像素比?

谢谢。

【问题讨论】:

什么是像素比?我使用 WinAPI 来获取屏幕的分辨率和物理尺寸,从那里我可以计算 DPI。这就是你想要的吗? 在 MacOSX backingScaleFactor 返回 2 用于视网膜显示,对于非视网膜它返回 1。msdn.microsoft.com/en-us/library/windows/desktop/… 这显示了获取比率的示例代码,GetDeviceCaps(hdc, LOGPIXELSX) / 96.0f;但有些如何它只返回 1 :( 顺便说一句,现在我正在安装在 macbook pro 视网膜上的 bootcamp 上的 win 8.1 上测试此代码。 【参考方案1】:

最后我找到了解决方案,使用下面的代码 sn-p 来获取比例因子,

FLOAT dpiX, dpiY;
HDC screen = GetDC(0);
dpiX = static_cast<FLOAT>(GetDeviceCaps(screen, LOGPIXELSX));
dpiY = static_cast<FLOAT>(GetDeviceCaps(screen, LOGPIXELSY));
ReleaseDC(0, screen);

FLOAT scaleFactor = dpiX / 96.0f; // this is same as devicePixelRatio

现在需要让您的应用了解 DPI。在 Project Settings > Manifest Tool > Input and Output 属性页中将 Enable DPI Awareness 标志设置为 Yes

【讨论】:

【参考方案2】:

以下是我计算 DPI 的方法。 请用实际的错误检查替换断言,因为其中一些情况有时会发生。

#include <QString>

#include <Windows.h>
#include <SetupApi.h>
#include <cfgmgr32.h>

#include <assert.h>
#include <vector>
#include <stdint.h>

const GUID GUID_CLASS_MONITOR =  0x4d36e96e, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 ;

// Assumes hEDIDRegKey is valid
bool GetMonitorSizeFromEDID(const HKEY hEDIDRegKey, short& WidthMm, short& HeightMm)

    DWORD dwType, AcutalValueNameLength = 128;
    TCHAR valueName[128];

    BYTE EDIDdata[1024];
    DWORD edidsize = sizeof(EDIDdata);

    for (LONG i = 0, retValue = ERROR_SUCCESS; retValue != ERROR_NO_MORE_ITEMS; ++i)
    
        retValue = RegEnumValue(hEDIDRegKey, i, &valueName[0],
            &AcutalValueNameLength, NULL, &dwType,
            EDIDdata, // buffer
            &edidsize); // buffer size

        if (retValue != ERROR_SUCCESS || QString(valueName) != "EDID")
            continue;

        WidthMm = ((EDIDdata[68] & 0xF0) << 4) + EDIDdata[66];
        HeightMm = ((EDIDdata[68] & 0x0F) << 8) + EDIDdata[67];

        return true; // valid EDID found
    

    return false; // EDID not found


bool GetSizeForDevID(const QString& TargetDevID, short& WidthMm, short& HeightMm)

    HDEVINFO devInfo = SetupDiGetClassDevsExA(
        &GUID_CLASS_MONITOR, //class GUID
        NULL, //enumerator
        NULL, //HWND
        DIGCF_PRESENT | DIGCF_PROFILE, // Flags //DIGCF_ALLCLASSES|
        NULL, // device info, create a new one.
        NULL, // machine name, local machine
        NULL);// reserved

    if (NULL == devInfo)
        return false;

    bool success = false;

    for (ULONG i = 0; ERROR_NO_MORE_ITEMS != GetLastError(); ++i)
    
        SP_DEVINFO_DATA devInfoData;
        memset(&devInfoData, 0, sizeof(devInfoData));
        devInfoData.cbSize = sizeof(devInfoData);

        if (SetupDiEnumDeviceInfo(devInfo, i, &devInfoData))
        
            CHAR Instance[MAX_DEVICE_ID_LEN];
            SetupDiGetDeviceInstanceIdA(devInfo, &devInfoData, Instance, MAX_DEVICE_ID_LEN, NULL);

            if (!QString(Instance).contains(TargetDevID))
                continue;

            HKEY hEDIDRegKey = SetupDiOpenDevRegKey(devInfo, &devInfoData,
                DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);

            if (!hEDIDRegKey || (hEDIDRegKey == INVALID_HANDLE_VALUE))
                continue;

            success = GetMonitorSizeFromEDID(hEDIDRegKey, WidthMm, HeightMm);
            RegCloseKey(hEDIDRegKey);

            if (success)
                break;
        
    
    SetupDiDestroyDeviceInfoList(devInfo);
    return success;


static bool DisplayDeviceFromHMonitor(HMONITOR hMonitor, DISPLAY_DEVICE& ddMonOut)

    MONITORINFOEX mi;
    mi.cbSize = sizeof(MONITORINFOEX);
    GetMonitorInfoA(hMonitor, &mi);

    DISPLAY_DEVICE dd;
    dd.cb = sizeof(dd);

    for (DWORD devIdx = 0; EnumDisplayDevicesA(nullptr, devIdx, &dd, 0); ++devIdx)
    
        if (QString(dd.DeviceName) != QString(mi.szDevice))
            continue;

        DISPLAY_DEVICE ddMon;
        memset(&ddMon, 0, sizeof(ddMon));
        ddMon.cb = sizeof(ddMon);
        if (EnumDisplayDevicesA(dd.DeviceName, 0, &ddMon, 0))
        
            ddMonOut = ddMon;
            return true;
        

        memset(&dd, 0, sizeof(dd));
        dd.cb = sizeof(dd);
    

    return false;


BOOL CALLBACK MonitorEnumProc(
    _In_  HMONITOR hMonitor,
    _In_  HDC /*hdcMonitor*/,
    _In_  LPRECT /*lprcMonitor*/,
    _In_  LPARAM context
    )


    std::vector<HMONITOR> * monitors = (std::vector<HMONITOR>*)context;
    assert(monitors);
    monitors->push_back(hMonitor);

    return TRUE;


uint32_t CSystemMetrics::screenDpi(void* window, const CRect& rect)

    // Identify the HMONITOR of interest via the callback MyMonitorEnumProc
    HDC dc = GetWindowDC((HWND)window);
    assert(dc);

    RECT windowRect;
    windowRect.top = rect.top;
    windowRect.left = rect.left;
    windowRect.right = rect.right;
    windowRect.bottom = rect.bottom;

    std::vector<HMONITOR> monitors;
    EnumDisplayMonitors(dc, rect.size().area() > 0 ? (&windowRect) : nullptr, MonitorEnumProc, (LPARAM)&monitors);
    ReleaseDC((HWND)window, dc);

    assert(!monitors.empty());

    DISPLAY_DEVICE ddMon;
    assert(DisplayDeviceFromHMonitor(monitors.front(), ddMon));

    MONITORINFO monitorInfo;
    monitorInfo.cbSize = sizeof(MONITORINFO);
    assert(GetMonitorInfoA(monitors.front(), &monitorInfo));

    const auto deviceIdSections = QString(ddMon.DeviceID).split("\\");
    assert(deviceIdSections.size() > 1);

    short widthMm, heightMm;
    assert(GetSizeForDevID(deviceIdSections[1], widthMm, heightMm));

    const float hDPI = (monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left) * 25.4f / widthMm;
    const float vDPI = (monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top) * 25.4f / heightMm;

    const uint32_t dpi = uint32_t(((float)hDPI + (float)vDPI / 2.0f) + 0.5f);

    assert(dpi < 700 && widthMm > 100 && heightMm > 100);

    return dpi;

【讨论】:

【参考方案3】:

现在一个简单的问题掩盖了一个复杂的问题 :-) 我无法真正回答这个问题,而只是提供一些小提示:

首先,Windows 没有 OSX 已知的设备像素与逻辑像素范例,因此您关于“设备像素比”的问题可能会引起非 OSX 开发人员的愤怒。

对于 Qt,从 4.x 版本到 5.x 版本有一些实质性的变化。事实上,您可能真的想考虑“升级”到 Qt 5。

http://doc.qt.io/qt-5/highdpi.html

引用此链接:

“Qt 5.4 为 Windows 和 Unix (XCB) 的平台插件引入了类似于 OS X 的按设备像素比进行缩放的实验性支持。”

另一方面,Qt 4 仅提供“可扩展应用程序”的那些技巧:

http://doc.qt.io/qt-4.8/scalability.html

相关的 SO 问题:

Automatic rescaling of an application on high-dpi Windows platform?

【讨论】:

是的,升级到 Qt 5 是完美的解决方案,但它比手动扩展 UI 项需要更多时间。我有点不同意 windows 没有设备与逻辑像素范例,因为在他们自己的文档中,他们提供了示例代码,他们正在创建大小为 640x480 的窗口,但将其缩放到系统 DPI。 FLOAT dpiX, dpiY; HDC screen = GetDC(0); dpiX = static_cast&lt;FLOAT&gt;(GetDeviceCaps(screen, LOGPIXELSX)); dpiY = static_cast&lt;FLOAT&gt;(GetDeviceCaps(screen, LOGPIXELSY)); ReleaseDC(0, screen); hWnd = CreateWindow( TEXT("DirectWriteApp"), TEXT("DirectWrite Demo App"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, static_cast&lt;INT&gt;(dpiX * 640.f / 96.f), static_cast&lt;INT&gt;(dpiY * 480.f / 96.f), NULL, NULL, hInstance, NULL ); 如果您无法回答问题,请发表评论。 看来我找到了问题所在。在链接器清单文件中添加 true 后,某些应用程序无法识别 DPI。相反,这个清单文件我们只需要在项目设置 > 清单工具 > 输入和输出 > 启用 DPI 感知中打开“启用 DPI 感知”选项。非常感谢您的快速积分.. @Violet Giraffe:我尝试发表评论而不是回答,但 SO 声誉政策不允许我...

以上是关于如何在 Qt 4.8 for Windows 中获取设备像素比?的主要内容,如果未能解决你的问题,请参考以下文章

如何在VS2012下静态构建Qt 4.8/5.2,使用静态MSVC运行时,支持Windows XP?

Windows 上的 Qt 5.1.0 使用 minGW 4.8 需要很长时间才能调试

如何在 qt 4.8 中通过 dbus 接收 QList<QVariantMap>?

Qt for Embedded Linux

QVFB 无法显示 -- QT for Embedded linux 演示示例

Qt5 和 Qt 4.8 中的模型和角色