定义
英文全称为Dots per inch,每英寸点数,监视器没有点,但有像素,在显示器上就是每英寸的像素个数。
Window上一般默认是96 dpi 作为100% 的缩放比率, 但是要注意的是该值未必是真正的显示器物理值, 只是Windows里我们的一个参考标准。
一般来说,缩放比例的步进值是25%,对应DPI的步进值为24,当然用户也可以定义任意缩放值。
缩放比例和DPI的关系:
100% - 96 DPI
125% - 120 DPI
150% - 144 DPI
175% - 168 DPI
200% - 192 DPI
依次类推
500% - 480 DPI
检测DPI的函数
Windows 8及之前版本仅支持系统级DPI——
可以先GetDC(NULL)获取桌面hDC,然后GetDeviceCaps(hDC, LOGPIXELSX和LOGPIXELSY)获取,最后别忘了ReleaseDC(hDC)。
Windows 8.1及之后版本支持逐显示器级DPI——
在C/C++程序中,可以使用MonitorFromPoint和GetDpiForMonitor获取指定显示器的DPI,也可以从WM_DPICHANGED消息的lParam参数中获取。
关于高DPI的支持
原因:
系统字体是是以固定大小(宋体10号字,物理尺寸为(10/72)英寸)设计的,在移动设备时代和大显示器的高分辨率时代,这样的字体太小了,要想在屏幕分辨率不变的前提下,让字体更大,就需要让字体占有更多的像素,通过提高DPI可以实现,
实现方式:
Vista/Win7/Win8
DWM(Desktop Window Manager) 虚拟化支持:通过DWM的缩放实现的, 具体过程是这样的, 比如我们当前系统的DPI是200%, 我们程序运行时,系统会告诉你当前DPI仍然是96(100%), 所以我们程序会仍然按照100%的方式进行绘画, 但是系统给我们的坐标是根据DPI缩小过后的(也就是我们对窗口调用GetWindowRect或是通过GetSystemMetrics(SM_CXSCREEN)得到的大小会比实际大小减半) , 当我们画完之后, DWM再对整个窗口进行200% 放大后画到屏幕上, 这样看起来我们的程序就自动支持高DPI了。
DWM缺点:
经过缩放后的内容看起来会变模糊, 比如文字会有明显的锯齿
如果程序已经支持高DPI,系统允许程序关闭DPI虚拟化,方法是调用user32.dll中的SetProcessDPIAware()函数,声明程序支持DPI缩放。
这个函数对整个进程起作用,也就是说DLL是不应该调用这个函数的。
在调用这个函数之前,通过GetDeviceCaps获得的DPI是96;调用这个函数之后,可通过GetDeviceCaps获得真实的DPI值。
获取程序的DPI支持状态,可使用user32.dll中的IsProcessDPIAware()函数。
系统还允许在程序的manifest(清单)资源(一个嵌入exe的xml配置文件)中声明程序支持DPI缩放,声明true相当于在程序启动时运行SetProcessDPIAware()函数。
Win8.1及以后
WIn8.1对高DPi以3种方式支持 Process_DPI_Awareness :
typedef enum _Process_DPI_Awareness {
Process_DPI_Unaware = 0,//不支持DPI缩放
Process_System_DPI_Aware = 1,//支持系统级DPI
Process_Per_Monitor_DPI_Aware = 2//支持逐显示器DPI
} Process_DPI_Awareness;
第一种Unaware, 该种方式是告诉系统, 我的程序不支持DPI aware, 请通过DWM虚拟化帮我们实现。 该方式和上面Win7/Win8对高DPI的支持的实现基本一样,主要区别是它通过GetWindowRect取到的坐标都是经过DWM缩放后的, 无论对方窗口是不是支持DWM虚拟化。
第二种方式是System DPI aware, 该方式下告诉系统, 我的程序会在启动的显示器上自己支持DPI aware, 所以不需要对我进行DWM 虚拟化。 但是当我的程序被拖动到其他DPI不一样的显示器时, 请对我们先进行system DWM虚拟化缩放。
第三种方式是Per Monitor DPI aware, 该方式是告诉系统, 请永远不要对我进行DWM虚拟化,我会自己针对不同的Monitor的DPi缩放比率进行缩放。
调用shcore.dll中的SetProcessDpiAwareness(int level)可以声明DPI缩放级别:0表示不支持DPI缩放,1表示支持系统级DPI,2表示支持逐显示器DPI
这个函数调用一次之后,进程的DPI缩放级别即被锁定无法更改,即使使用老的SetProcessDPIAware函数也不行。
老版本的SetProcessDPIAware()调用等价于SetProcessDpiAwareness(1),它也会锁定DPI缩放级别。
所以SetProcessDpiAwareness必须在任何指定DPI缩放级别的操作之前调用,否则无效。
在级别0,无论是GetDeviceCaps还是GetDpiForMonitor都会返回96;
在级别1,两个函数都会返回系统级DPI的值;
在级别2,GetDeviceCaps返回系统级DPI,而GetDpiForMonitor返回指定显示器的DPI,同时窗口还会收到WM_DPICHANGED消息。
获得某个进程的DPI缩放级别,可以使用shcore.dll中的GetProcessDpiAwareness函数。
Windows 8.1和之前的操作系统中,manifest资源中的声明的等效函数调用也不同:
false —— SetProcessDpiAwareness(0) —— 什么也不做
true —— SetProcessDpiAwareness(1) —— SetProcessDPIAware()
true/pm —— SetProcessDpiAwareness(2) —— SetProcessDPIAware()
per monitor —— SetProcessDpiAwareness(2) —— 什么也不做
相关API:
SetProcessDpiAwareness :设置当前进程对高DPi的支持方式
GetProcessDpiAwareness :查询某个进程对高DPI的支持方式
GetDpiForMonitor : 获取某个Monitor的DPI
WM_DPICHANGED :当某个程序窗口被拖到另外一个DPI的Monitor时收到
VS设置方式
View->Property Pages->Configuration->Manifest Tool->Input and Output->DPI Awareness, 默认设置High Dpi Aware