DPI设置与获取之前请保证程序已经打开DPI感知或者在清单文件嵌入DPI感知
,否则API可能获取的值不准确
方法一:GetScaleFactorForMonitor
通过枚举显示器获取不同设备的DPI,获取的是实际DPI
static BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor,HDC hdcMonitor,LPRECT lprcMonitor,LPARAM dwData)
{std::vector<int> *pvec_ret = (std::vector<int> *)(dwData);DEVICE_SCALE_FACTOR val = DEVICE_SCALE_FACTOR_INVALID;GetScaleFactorForMonitor(hMonitor, &val);if (val != DEVICE_SCALE_FACTOR_INVALID)pvec_ret->push_back(val);return TRUE;
}std::vector<int> DpiHelper::GetDpiFromMonitors()
{std::vector<int> vec_ret;do{HDC hdc = GetDC(NULL);if (hdc == nullptr){OutputDebugStringA("DpiHelper::GetDpiFromMonitors GetDC failed");break;}EnumDisplayMonitors(hdc, NULL, MonitorEnumProc, (LPARAM)&vec_ret);ReleaseDC(NULL,hdc);} while (false);if (vec_ret.empty())vec_ret.push_back(100);return vec_ret;
}
方法二:DisplayConfigGetDeviceInfo
实测过程中发现有时候该值返回的与实际值不一致。例如当系统锁定缩放200%,通过显示设置查看的值200%,但当打开自定义缩放的时候发现实际值是100%。此时该接口返回的是200%
具体代码如下:
DpiHelper.h
#pragma once
#include <Windows.h>
#include <vector>/*
* OS reports DPI scaling values in relative terms, and not absolute terms.
* eg. if current DPI value is 250%, and recommended value is 200%, then
* OS will give us integer 2 for DPI scaling value (starting from recommended
* DPI scaling move 2 steps to the right in this list).
* values observed (and extrapolated) from system settings app (immersive control panel).
*/
static const UINT32 DpiVals[] = { 100,125,150,175,200,225,250,300,350, 400, 450, 500 };class DpiHelper
{
public:template<class T, size_t sz>static size_t CountOf(const T (&arr)[sz]){UNREFERENCED_PARAMETER(arr);return sz;}/** @brief : Use QueryDisplayConfig() to get paths, and modes.* @param[out] pathsV : reference to a vector which will contain paths* @param[out] modesV : reference to a vector which will contain modes* @param[in] flags : determines the kind of paths to retrieve (only active paths by default)* return : false in case of failure, else true*/static bool GetPathsAndModes(std::vector<DISPLAYCONFIG_PATH_INFO>& pathsV, std::vector<DISPLAYCONFIG_MODE_INFO>& modesV, int flags = QDC_ONLY_ACTIVE_PATHS);//out own enum, similar to DISPLAYCONFIG_DEVICE_INFO_TYPE enum in wingdi.henum class DISPLAYCONFIG_DEVICE_INFO_TYPE_CUSTOM : int{DISPLAYCONFIG_DEVICE_INFO_GET_DPI_SCALE = -3, //returns min, max, suggested, and currently applied DPI scaling values.DISPLAYCONFIG_DEVICE_INFO_SET_DPI_SCALE = -4, //set current dpi scaling value for a display};/** struct DISPLAYCONFIG_SOURCE_DPI_SCALE_GET* @brief used to fetch min, max, suggested, and currently applied DPI scaling values.* All values are relative to the recommended DPI scaling value* Note that DPI scaling is a property of the source, and not of target.*/struct DISPLAYCONFIG_SOURCE_DPI_SCALE_GET{DISPLAYCONFIG_DEVICE_INFO_HEADER header;/** @brief min value of DPI scaling is always 100, minScaleRel gives no. of steps down from recommended scaling* eg. if minScaleRel is -3 => 100 is 3 steps down from recommended scaling => recommended scaling is 175%*/std::int32_t minScaleRel;/** @brief currently applied DPI scaling value wrt the recommended value. eg. if recommended value is 175%,* => if curScaleRel == 0 the current scaling is 175%, if curScaleRel == -1, then current scale is 150%*/std::int32_t curScaleRel;/** @brief maximum supported DPI scaling wrt recommended value*/std::int32_t maxScaleRel;};/** struct DISPLAYCONFIG_SOURCE_DPI_SCALE_SET* @brief set DPI scaling value of a source* Note that DPI scaling is a property of the source, and not of target.*/struct DISPLAYCONFIG_SOURCE_DPI_SCALE_SET{DISPLAYCONFIG_DEVICE_INFO_HEADER header;/** @brief The value we want to set. The value should be relative to the recommended DPI scaling value of source.* eg. if scaleRel == 1, and recommended value is 175% => we are trying to set 200% scaling for the source*/int32_t scaleRel;};/** struct DPIScalingInfo* @brief DPI info about a source* mininum : minumum DPI scaling in terms of percentage supported by source. Will always be 100%.* maximum : maximum DPI scaling in terms of percentage supported by source. eg. 100%, 150%, etc.* current : currently applied DPI scaling value* recommended : DPI scaling value reommended by OS. OS takes resolution, physical size, and expected viewing distance* into account while calculating this, however exact formula is not known, hence must be retrieved from OS* For a system in which user has not explicitly changed DPI, current should eqaul recommended.* bInitDone : If true, it means that the members of the struct contain values, as fetched from OS, and not the default* ones given while object creation.*/struct DPIScalingInfo{UINT32 mininum = 100;UINT32 maximum = 100;UINT32 current = 100;UINT32 recommended = 100;bool bInitDone = false;};DpiHelper();~DpiHelper();//当锁定的时候获取的值可能不对,例如dpi锁定200%,该函数获取的是200,但是实际是100static DpiHelper::DPIScalingInfo GetDPIScalingInfo(LUID adapterID, UINT32 sourceID);static bool SetDPIScaling(LUID adapterID, UINT32 sourceID, UINT32 dpiPercentToSet);//当锁定的时候获取的值可能不对,例如dpi锁定200% 但是实际是100,则获取实际值100static std::vector<int> GetDpiFromMonitors();
};
DpiHelper.cpp
#include "DpiHelper.h"
#include <memory>
#include <cassert>
#include<Windows.h>
#include<shtypes.h>
#include<shellscalingapi.h>bool DpiHelper::GetPathsAndModes(std::vector<DISPLAYCONFIG_PATH_INFO>& pathsV, std::vector<DISPLAYCONFIG_MODE_INFO>& modesV, int flags)
{UINT32 numPaths = 0, numModes = 0;auto status = GetDisplayConfigBufferSizes(flags, &numPaths, &numModes);if (ERROR_SUCCESS != status){return false;}std::unique_ptr<DISPLAYCONFIG_PATH_INFO[]> paths(new DISPLAYCONFIG_PATH_INFO[numPaths]);std::unique_ptr<DISPLAYCONFIG_MODE_INFO[]> modes(new DISPLAYCONFIG_MODE_INFO[numModes]);status = QueryDisplayConfig(flags, &numPaths, paths.get(), &numModes, modes.get(), nullptr);if (ERROR_SUCCESS != status){return false;}for (unsigned int i = 0; i < numPaths; i++){pathsV.push_back(paths[i]);}for (unsigned int i = 0; i < numModes; i++){modesV.push_back(modes[i]);}return true;
}DpiHelper::DpiHelper()
{
}DpiHelper::~DpiHelper()
{
}DpiHelper::DPIScalingInfo DpiHelper::GetDPIScalingInfo(LUID adapterID, UINT32 sourceID)
{DPIScalingInfo dpiInfo = {};DpiHelper::DISPLAYCONFIG_SOURCE_DPI_SCALE_GET requestPacket = {};requestPacket.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE)DpiHelper::DISPLAYCONFIG_DEVICE_INFO_TYPE_CUSTOM::DISPLAYCONFIG_DEVICE_INFO_GET_DPI_SCALE;requestPacket.header.size = sizeof(requestPacket);assert(0x20 == sizeof(requestPacket));//if this fails => OS has changed somthing, and our reverse enginnering knowledge about the API is outdatedrequestPacket.header.adapterId = adapterID;requestPacket.header.id = sourceID;auto res = ::DisplayConfigGetDeviceInfo(&requestPacket.header);if (ERROR_SUCCESS == res){//successif (requestPacket.curScaleRel < requestPacket.minScaleRel){requestPacket.curScaleRel = requestPacket.minScaleRel;}else if (requestPacket.curScaleRel > requestPacket.maxScaleRel){requestPacket.curScaleRel = requestPacket.maxScaleRel;}std::int32_t minAbs = abs((int)requestPacket.minScaleRel);if (DpiHelper::CountOf(DpiVals) >= (size_t)(minAbs + requestPacket.maxScaleRel + 1)){//all okdpiInfo.current = DpiVals[minAbs + requestPacket.curScaleRel];dpiInfo.recommended = DpiVals[minAbs];dpiInfo.maximum = DpiVals[minAbs + requestPacket.maxScaleRel];dpiInfo.bInitDone = true;}else{//Error! Probably DpiVals array is outdatedreturn dpiInfo;}}else{//DisplayConfigGetDeviceInfo() failedreturn dpiInfo;}return dpiInfo;
}bool DpiHelper::SetDPIScaling(LUID adapterID, UINT32 sourceID, UINT32 dpiPercentToSet)
{DPIScalingInfo dPIScalingInfo = GetDPIScalingInfo(adapterID, sourceID);if (dpiPercentToSet == dPIScalingInfo.current){return true;}if (dpiPercentToSet < dPIScalingInfo.mininum){dpiPercentToSet = dPIScalingInfo.mininum;}else if (dpiPercentToSet > dPIScalingInfo.maximum){dpiPercentToSet = dPIScalingInfo.maximum;}int idx1 = -1, idx2 = -1;int i = 0;for (const auto& val : DpiVals){if (val == dpiPercentToSet){idx1 = i;}if (val == dPIScalingInfo.recommended){idx2 = i;}i++;}if ((idx1 == -1) || (idx2 == -1)){//Error cannot find dpi valuereturn false;}int dpiRelativeVal = idx1 - idx2;DpiHelper::DISPLAYCONFIG_SOURCE_DPI_SCALE_SET setPacket = {};setPacket.header.adapterId = adapterID;setPacket.header.id = sourceID;setPacket.header.size = sizeof(setPacket);assert(0x18 == sizeof(setPacket));//if this fails => OS has changed somthing, and our reverse enginnering knowledge about the API is outdatedsetPacket.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE)DpiHelper::DISPLAYCONFIG_DEVICE_INFO_TYPE_CUSTOM::DISPLAYCONFIG_DEVICE_INFO_SET_DPI_SCALE;setPacket.scaleRel = (UINT32)dpiRelativeVal;auto res = ::DisplayConfigSetDeviceInfo(&setPacket.header);if (ERROR_SUCCESS == res){return true;}else{return false;}return true;
}static BOOL CALLBACK MonitorEnumProc(HMONITOR hMonitor,HDC hdcMonitor,LPRECT lprcMonitor,LPARAM dwData)
{std::vector<int> *pvec_ret = (std::vector<int> *)(dwData);DEVICE_SCALE_FACTOR val = DEVICE_SCALE_FACTOR_INVALID;GetScaleFactorForMonitor(hMonitor, &val);if (val != DEVICE_SCALE_FACTOR_INVALID)pvec_ret->push_back(val);return TRUE;
}std::vector<int> DpiHelper::GetDpiFromMonitors()
{std::vector<int> vec_ret;do{HDC hdc = GetDC(NULL);if (hdc == nullptr){OutputDebugStringA("DpiHelper::GetDpiFromMonitors GetDC failed");break;}EnumDisplayMonitors(hdc, NULL, MonitorEnumProc, (LPARAM)&vec_ret);ReleaseDC(NULL,hdc);} while (false);if (vec_ret.empty())vec_ret.push_back(100);return vec_ret;
}