理解windows DPI 以及开发过程中的迷惑问题

一、DPI定义

DPI全称是dots per inch, 也就是每英寸的点数,在显示器上就是每英寸的像素个数,Window上一般默认是96 dpi 作为100% 的缩放比率, 但是要注意的是该值未必是真正的显示器物理值, 只是Windows里我们的一个参考标准。

二、为什么设置DPI会使我们看到的程序变大

简单来说我们可以直接理解为系统的机制,我们暂时不讨论程序对DPI的适配,只讨论系统在设置125%以后程序会放大的原理,如果打个比方的话就像系统使用个放大镜一样让我们的程序看起来变大了,但是实际上程序还是那么大,只不过视觉上看起来按照设置的DPI比例放大了而已。

如果讨论程序对DPI的适配,也就是说,程序感知到了桌面的dpi变为125%,那么程序在感知到桌面的dpi变化之后,程序把自身大小调成125%的大小,这个改变是与系统无关的,程序只是感知到系统DPI的变化,从而自己按照系统DPI去适配自己程序放大后的效果。这个是程序级别的放大,所以如果程序本身对系统DPI有所感知,我们看到的125%分辨率下的程序就不是简单的模糊放大,可能是实际上的像素点变多(也有程序感知到系统DPI为125%以后却还保持自己的程序界面为100%的大小,例如生死狙击桌面版本)。

举个例子理解,在Win10系统下我们原生DPI100%,然后去量一个窗口的大小为100*100,如果此时我们把DPI设置为200%,我们会发现视觉上窗口变大了两倍,但是实际上窗口细节是模糊的,这就是因为系统使用了DWM Desktop Window  Manager)虚拟化机制去帮助程序适配高分辨率。

DPI比例与每英寸点数对应关系:

 

屏幕比例

每英寸点数

100%

96

125%

120

150%

144

175

192

 

注:以上的每英寸点数为代码中关于DPI问题经常使用到的数字

 

三、不同系统下的DWM机制

对于系统dpi的改善,可以分为三个时间段

1. WindowsXP阶段:

对高DPI的支持比较差劲, 大部分情况下就是字体的放大, 当然我们程序也可以通过GetDeviceCaps(hDC, LOGPIXELSX)获取DPI后自己对绘画的内容进行缩放。因为现在已经用不到了,不作过多讨论。

2.Windows Vista/Windows 7/Windows 8阶段:

Windows Vista / 7 / 8 中,操作系统提供了真正的 DPI 的设置:

Windows 7 中还额外提供了传统 Windows XP 风格 DPI 缩放比例的选项(此选项在 Windows 8 之后就删掉了),这也是在修改 DPI 值,只不过可以选择非1/4 整数倍的 DPI 值。

以上系统下我们可以禁止DWM, 该模式我们称之为Basic模式, 这种模式下的DPI效果和XP一样。如何禁止:在服务管理器中找Desktop Window Manager Session Manager 这个选项就可以禁止但是在不禁止DWM模式,我们就可以看到以上已经讨论过的放大镜效果了

 3.Windows 8.1以后阶段

Windows 10 中的多个屏幕选择

▲ Windows 10 中的多个屏幕选择

Windows 10 中针对每个屏幕的 DPI 设置

▲ Windows 10 中针对每个屏幕的 DPI 设置

 

如果用户在设置中更改了系统 DPI 值或屏幕 DPI 值,那么 Windows 系统会提示需要注销才会应用修改

对于 Windows 8.1 以下的系统,注销是必要的。因为系统 DPI 值如果不注销就不会改变,应用需要在系统重新登录后有了新的 DPI 值时才会正常根据新的系统 DPI 值进行渲染。否则就是系统进行的位图缩放。

对于 Windows 8.1 及以上的系统,注销通常也是必要的。虽然屏幕 DPI 值已经更新,并且已向应用窗口发送了 Dpi Change 消息,但系统 DPI 值依然没变。应用必须处理 Dpi Change 消息才会正常渲染。如果应用不支持屏幕 DPI 感知(后边讨论),那么使用的就是系统 DPI 值,于是一样的会被系统进行位图缩放(放大镜)。

 

但事情到 Windows 10 (1803)  之后,事情又有了转机。现在,你可以通过在设置中打开一个开关,使得无需注销,只要重新打开应用即可让此应用获取到最新的系统 DPI 的值。

Windows 10 (1803) 中新增的不模糊设置项

方法是:打开设置” ­> “系统” ­> “显示器” ­> “高级缩放设置,在高级缩放设置上,打开允许 Windows 尝试修复应用,使其不模糊。额外的,对于 Windows 8.1 及以上的系统,系统 DPI 值等于主屏在系统启动时的屏幕 DPI 值。

四、对 Windows 应用而言的 DPI 感知级别(Dpi Awareness

上面已经说过系统对于DPI的操作,我们再讨论一下应用程序,对于系统dpi改变后的适配方式。现在Windows 的程序 DPI 感知级别经过历代升级,主要是有四种级别。

 

1.DPI Unaware

顾名思义,就是程序对系统的DPI不进行感知,对于应用程序本身,永远是96DPI,但是在系统级别的DPI改变时,系统直接对该感知级别的程序进行DWM放大,就是视觉上模糊的位图扩大,坐标位置还是不变。这是操作系统进行的操作。简单理解就是应用程序告诉系统,本程序对于DPI改变没有适配,你操作系统帮我给用户适配一下吧。这种感知级别的程序对于调用系统的API获取系统的DPI时,获取到的永远都是100%,而且调用相关的系统API时例如GetWindowRect去获取其他窗口的Rect时,获取到的数据无论窗口实际的大小为多少,获取的永远都是该窗口在100%系统分辨率下所在的位置以及长度与宽度。

2.System DPI Awareness

这个级别的程序,在系统启动的时候,系统所设置的DPI程序会去识别这个DPI,然后根据这个DPI来选择自己程序编好的DPI进行识别,这个是程序层面的DPI适配,在初始化期间,他们使用该系统DPI值适当地布置其UI(大小控制,选择字体大小,加载资源等)。这种适配不是模糊的看起来扩大,而是程序真真切切的变大了,这是程序级别的操作。当然有些程序获取到了系统的DPI以后,他可能不随系统的DPI去设置自己程序窗口的DPI,反而让自己的程序在125%的情况下依然显示的大小是100%的大小,可以去看一下生死狙击微端登录这一款游戏。

但是假设我们是双屏的屏幕,一个主屏幕是125%,另一个屏幕是150%,然后程序启动感受到了125%的桌面DPI,然后初始化的时候使用程序125%的初始化方式,但是当程序从125%的屏幕移动到150%的屏幕时候,程序不会再进行150%的初始化,而是让系统对其进行模糊的位图放大,这个放大就是操作系统层面的。

所以来说这个级别的程序只是在初始化的时候去获取一下DPI然后根据自己程序的情况去适配系统的DPI

3.Per­Monitor

此模式下的程序是告诉系统,不要对我使用DWM放大,当系统DPI更改时,给程序发送WM_DPICHANGED消息,应用程序自己负责为DPI处理自身的大小调整。桌面应用程序使用的大多数UI框架(Windows通用控件(comctl32),Windows窗体,Windows Presentation Framework等)都不支持自动DPI缩放,需要开发人员自行调整其窗口内容的大小并重新放置其内容。

4.Per­Monitor (V2)

Per—Monitor的模式相同,主要差别为以下几点:

  1. DPI更改时(顶级HWND和子HWND通知应用程序
  2. 该应用程序看到每个显示器的原始像素
  3. Windows永远不会缩放应用程序
  4. Windows自动缩放非客户区域(窗口标题,滚动条等)
  5. Win32对话框(来自CreateDialog)由Windows自动缩放DPI
  6. 公共控件(复选框,按钮背景等)中以主题绘制的位图资产将以适当的DPI比例因子自动呈现

Per­Monitor v2模式下运行时,当DPI更改时,将通知应用程序。如果应用程序没有为新的DPI调整大小,则应用程序UI将显得太小或太大(取决于先前DPI值和新DPI值的不同)。

对于应用程序的DPI感知级别可以在Win10下的任务管理器中查看

打开任务管理器,右键点击窗口栏,就可以弹出程序显示情况,勾选DPI感知,就可以在详细里面看到每个程序的DPI感知模式。

其他更深入的知识请移步MSDN High DPI Desktop Application Development on Windows

 

五、如何设置程序的DPI感知以及相关的API使用

理解了上面程序的DPI感知级别,我们再讲一下如何去设置程序的DPI感知级别,主要有以下两个办法:(方法名是我在MSDN上复制过来的,翻译过来很生硬,大家自行理解)

  1. through an application manifest setting
  2. programmatically through an API call

 

在此主要解释一下第二种方法,使用windows提供的API进行设置,就是以编程方式设置程序的DPI感知,虽然Windows不建议我们这么做,但是在我们的开发过程中,这些API还是挺实用的,以及其他相关程序DPI感知行为的API解释。

这些API有三个版本的迭代,以SetProcessDpiAwarenessContext系列为例

 

API

Windows

最低支持

版本

DPI Unaware

System DPI Aware

Per Monitor D

SetProcessDpiAware

Windows

Vista

N/A

SetProcessDpiAware()

N/A

SetProcessDpiAwareness

Windows

8.1

PROCESS_DPI_UNAWARE

PROCESS_DPI_SYSTEM_DPI_AWARE

PROCESS_DPI

SetProcessDpiAwarenessContext

Windows

10,version 1607

DPI_AWARENESS_CONTEXT_UNAWARE

DPI_AWARENESS_CONTEXT_SYSTEM_AWARE

DPI_AWARENE

SetProcessDpiAwareness ()函数的参数枚举:

SetProcessDpiAwarenessContext()函数的参数枚举:

DPI系列的相关函数还有获取程序DPI感知等,详细可去MSDN上查看

六、日常DPI相关开发时容易遇到的问题

1、在自身程序为DPI不感知的情况下,调用系统API去获取DPI时,获取到的数据永远是100%的DPI

包括获取其他窗口大小,都是100%的情况。这一系列函数包括但不限于:

- BOOL GetWindowRect(HWND hWnd,LPRECT lpRect); 

看图中的例子可以想到该博主为什么获取到的Rect还要去乘一下系统缩放大小,因为他的程序是不感知系统DPI的级别,所以调用这个API需要去乘一下当前系统的缩放大小才能获取到真正实际的Rect。但是如果你的程序是DPI感知的情况,获取到的Rect就是实际上的Rect,不需要再去乘以系统的缩放大小了。

 

- int GetDeviceCaps(HDC hdc, int nlndex);

使用这个API去获取系统dpi像素点,这个跟上面的解释是一样的。

- GetDpiForSystem();

看一下这个函数的MSDN就可以明白

其他相关的一系列:GetDpiForWindowGetSystemDpiForProcess等等。

所以说当自己的程序为不感知系统DPI的情况下,我们调用系统提供的相关API都是获取不到正确的,如果想要获取到正确的DPI我们其实有两种方案:

1.通过读取注册表键值去获取

这种方法的局限就是你需要重启电脑才能获取到。键值位置如下图。

2.通过逻辑宽度与物理宽度比值计算获取桌面DPI

但是这个函数在程序DPI感知的情况下获取的大小永远是1,代码如下,只适用于当前DPI不感知的程序去获取当前系统DPI。win10下不需要重启就能获取到。

double GetDesktopDpiByCalcu()
{
	double dDpi = 0.0;
	// 获取窗口当前显示的监视器
	// 使用桌面的句柄.
	HWND hWnd = GetDesktopWindow();
	HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);

	// 获取监视器逻辑宽度与高度
	MONITORINFOEX miex;
	miex.cbSize = sizeof(miex);
	GetMonitorInfo(hMonitor, &miex);
	int cxLogical = (miex.rcMonitor.right - miex.rcMonitor.left);
	int cyLogical = (miex.rcMonitor.bottom - miex.rcMonitor.top);

	// 获取监视器物理宽度与高度
	DEVMODE dm;
	dm.dmSize = sizeof(dm);
	dm.dmDriverExtra = 0;
	EnumDisplaySettings(miex.szDevice, ENUM_CURRENT_SETTINGS, &dm);
	int cxPhysical = dm.dmPelsWidth;
	int cyPhysical = dm.dmPelsHeight;

	// 缩放比例计算  实际上使用任何一个即可
	double horzScale = ((double)cxPhysical / (double)cxLogical);
	double vertScale = ((double)cyPhysical / (double)cyLogical);
	double x = floor(horzScale * 100.00f + 0.5) / 100.00f;
	double y = floor(vertScale * 100.00f + 0.5) / 100.00f;
	if (x == y)//计算有偏差保留小数点后3位进行比较
	{
		dDpi = x;
	}

	return dDpi;
}

2、在自身程序为DPI感知的情况下,调用系统API去获取DPI可以获取到正确的

所以说你可以调用之前标题五中所讲的方式去设置自己程序的DPI感知级别。最简单的就是SetProcessDPIAware,使用这个函数以后,再去调用以上系统API就能获取到正确的系统DPI,但是Windows好像不太建议这么去设置自己程序的DPI。

 

 

 

 

 

 

 

 

 

 

<think>好的,用户需要了解如何在Python中获取Windows的实际缩放比例,并将缩放比例转换为数值,例如150%对应1.5。我得先想想有哪些方法可以实现这个功能。 首先,我回忆一下,Windows的缩放设置可能存储在注册表中。常见的位置是HKEY_CURRENT_USER\Control Panel\Desktop下的LogPixels值。不过可能需要结合不同的DPI感知设置来正确获取,特别是在高DPI环境下的不同应用程序行为。 另一个方法是通过Win32 API,比如使用GetScaleFactorForMonitor或GetDpiForMonitor函数。但直接在Python中调用这些API可能需要借助ctypes库或者pywin32这样的第三方库。用户可能更希望用标准库,所以可能需要用ctypes。 另外,还可以考虑使用系统环境变量或者检查显示设置的信息。不过环境变量可能不直接提供缩放比例,所以注册表或API方法更可靠。 接下来,我需要确定如何从注册表中读取LogPixels的值。注册表中的LogPixels值是一个DWORD,例如96 DPI对应100%缩放,所以LogPixels的值是96。当缩放比例改变时,这个值会按比例变化,例如144对应150%(96*1.5=144)。因此,缩放比例可以通过LogPixels的值除以96得到。 但是需要注意,不同的显示器可能有不同的缩放比例,特别是多显示器的情况下。这时候可能需要枚举显示器并获取每个的缩放比例。不过用户的问题可能只需要主显示器的缩放比例,所以先处理主显示器的情况。 使用ctypes来访问注册表的话,需要注意权限问题,可能需要以管理员权限运行,但通常读取这个键应该不需要管理员权限。需要处理可能的异常,比如键不存在的情况。 另外,系统可能有不同的DPI感知模式,如果程序没有声明为DPI感知,Windows可能会虚拟化DPI,导致获取的值不准确。因此,在获取之前可能需要设置进程的DPI感知模式,比如使用SetProcessDPIAware或更高版本的API。不过这部分在Python中如何实现需要查证。 另一个可能性是使用winreg模块来读取注册表,这可能比ctypes更简单。例如,用winreg打开HKEY_CURRENT_USER下的Control Panel\Desktop,然后读取LogPixels的值。不过需要注意,LogPixels可能位于不同的位置,比如有些资料提到Win8之后的系统可能将DPI设置放在HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics下的AppliedDPI,或者不同的键。可能需要进一步确认注册表路径。 或者,使用Windows的COM对象或WMI查询,但可能比较复杂。Python中可以使用WMI模块,但需要安装额外的库,用户可能希望不依赖第三方库。 综合来看,可能的方法有: 1. 读取注册表中的LogPixels或AppliedDPI值,计算缩放比例。 2. 使用ctypes调用Win32 API获取DPI信息。 3. 使用pywin32或其他库的封装方法。 现在,我需要验证这些方法的可行性。 首先,尝试注册表方法。对于Windows 10,缩放比例可能存储在HKEY_CURRENT_USER\Control Panel\Desktop的LogPixels值,或者WindowMetrics下的AppliedDPI。需要检查实际注册表的情况。假设用户使用的是较新的Windows版本,可能AppliedDPI更准确。例如,在注册表中查找HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics下的AppliedDPI,这是一个DWORD值,例如96对应100%,144对应150%。那么缩放比例就是AppliedDPI / 96。 但需要确定正确的注册表路径和键名。比如,一些资料指出在Windows 8及更高版本中,AppliedDPI存储了用户设置的DPI值,而LogPixels可能被动态更新。因此,读取AppliedDPI可能更可靠。 另一个可能的键是HKEY_CURRENT_USER\Software\Microsoft\Windows\DPIScaling下的AppliedDPI,但需要确认。 假设正确的位置是HKEY_CURRENT_USER\Control Panel\Desktop下的LogPixels,那么步骤是: 1. 使用winreg模块打开该键。 2. 读取LogPixels的值。 3. 计算缩放比例:scaling_factor = log_pixels / 96.0 但需要注意,某些情况下LogPixels可能不存在,或者系统使用不同的存储方式。此时可能需要回退到其他方法。 另一种方法是通过ctypes调用GetDpiForSystem或GetDpiForWindow函数,或者使用MonitorFromWindow和GetDpiForMonitor来获取当前显示器的DPI。例如,GetDpiForSystem返回系统的DPI,这可能基于主显示器。 假设使用GetDpiForSystem,返回的DPI值除以96即可得到缩放比例。例如,120%缩放对应115.2的DPI(但实际可能是120%,即1.2倍,所以96*1.2=115.2,所以缩放比例为115.2/96=1.2)。 但需要确保进程的DPI感知模式正确,否则可能获取到错误的值。例如,如果程序没有声明为DPI感知,GetDpiForSystem可能返回系统DPI的虚拟化值,而不是实际的。因此,在调用之前,可能需要设置进程为DPI感知。 在Python中,可以使用ctypes来调用SetProcessDPIAware或更高版本,如SetProcessDpiAwareness。例如: import ctypes ctypes.windll.shcore.SetProcessDpiAwareness(1) 然后再调用GetDpiForSystem。 但需要注意,不同Windows版本支持的API不同。SetProcessDpiAwareness在Windows 8.1及以上可用,而SetProcessDPIAware在Vista及以上可用。 因此,可能需要先检查系统版本,或者根据可用性选择合适的API。 不过,如果用户只需要主显示器的缩放比例,可能综合注册表和API方法会更可靠。 现在,编写代码的大致步骤: 方法一:通过注册表获取AppliedDPI或LogPixels: 1. 导入winreg模块。 2. 打开HKEY_CURRENT_USER\Control Panel\Desktop或相关子键。 3. 读取AppliedDPI或LogPixels的值。 4. 计算缩放比例。 方法二:使用ctypes调用GetDpiForSystem: 1. 设置进程DPI感知。 2. 调用GetDpiForSystem获取DPI值。 3. 计算缩放比例。 可能需要处理不同情况,例如多显示器,但用户可能只需要主显示器的缩放比例。 现在,可能需要编写示例代码来测试这两种方法。 例如,方法一的代码: import winreg def get_scaling_factor(): try: key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Control Panel\Desktop") value, _ = winreg.QueryValueEx(key, "LogPixels") scaling_factor = value / 96 return scaling_factor except Exception as e: print("Error accessing registry:", e) return None 但需要确认LogPixels是否存在,或者是否有其他键。比如,可能AppliedDPI在HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics下? 例如: key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Control Panel\Desktop\WindowMetrics") value, _ = winreg.QueryValueEx(key, "AppliedDPI") 如果AppliedDPI存在,则直接用它除以96即可。 否则,可能需要进一步查找。 如果注册表方法不可靠,可能需要转向API方法。 方法二的代码: import ctypes def get_scaling_factor(): try: # 设置DPI感知 ctypes.windll.shcore.SetProcessDpiAwareness(1) except Exception as e: # 可能旧版本Windows不支持,回退到SetProcessDPIAware ctypes.windll.user32.SetProcessDPIAware() hwnd = ctypes.windll.user32.GetDesktopWindow() dpi = ctypes.windll.user32.GetDpiForWindow(hwnd) scaling_factor = dpi / 96 return scaling_factor 不过,可能需要检查hwnd是否有效,或者是否应该使用其他函数,如GetDpiForSystem。 另外,GetDpiForSystem返回系统DPI,这可能更直接。 例如: dpi = ctypes.windll.user32.GetDpiForSystem() scaling_factor = dpi / 96 但需要确认这些函数的存在和参数。需要查阅Microsoft文档,确定函数原型是否正确。 例如,GetDpiForSystem的原型是: UINT GetDpiForSystem(); 在ctypes中,可以声明为: ctypes.windll.user32.GetDpiForSystem.restype = ctypes.c_uint ctypes.windll.user32.GetDpiForSystem.argtypes = [] 然后调用: dpi = ctypes.windll.user32.GetDpiForSystem() 如果成功的话,就能得到正确的DPI值。 如果用户系统不支持GetDpiForSystem(比如Windows版本低于10 1607),则可能需要使用其他方法,比如GetDeviceCaps。 例如,通过获取屏幕的DC,然后调用GetDeviceCaps获取LOGPIXELSX: hdc = ctypes.windll.user32.GetDC(None) dpi = ctypes.windll.gdi32.GetDeviceCaps(hdc, 88) # 88对应LOGPIXELSX ctypes.windll.user32.ReleaseDC(None, hdc) scaling_factor = dpi / 96 这种方法可能更兼容旧版本Windows,但同样需要正确的DPI感知设置,否则可能得到错误的值。 综上所述,可能需要结合多种方法,并根据系统版本选择合适的API。 总结可能的步骤: 1. 尝试使用API获取当前DPI,考虑进程的DPI感知设置。 2. 如果API不可用或失败,尝试读取注册表中的值。 3. 处理可能的异常和不同情况,如多显示器。 但用户的问题可能只需要一个简单的方法,所以可能需要提供一个示例代码,结合注册表和API方法。 现在,我需要确定哪种方法在大多数情况下有效。例如,注册表中的AppliedDPI可能更直接,但需要正确的位置。根据一些资料,AppliedDPI在HKEY_CURRENT_USER\Control Panel\Desktop下可能不存在,而是在HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics下的AppliedDPI。 例如,检查该路径: key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Control Panel\Desktop\WindowMetrics") value, _ = winreg.QueryValueEx(key, "AppliedDPI") 如果这个存在,那么缩放比例就是value / 96。否则,可能需要查找其他键。 假设AppliedDPI在WindowMetrics键下,那么正确的注册表路径是: HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics 并且键名为AppliedDPI。 例如,当缩放比例为150%时,AppliedDPI的值是144(96*1.5),所以缩放比例是144/96=1.5。 测试代码: import winreg def get_scaling_factor(): try: key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Control Panel\Desktop\WindowMetrics") applied_dpi, _ = winreg.QueryValueEx(key, "AppliedDPI") scaling_factor = applied_dpi / 96.0 return scaling_factor except FileNotFoundError: print("AppliedDPI not found in registry") return None except Exception as e: print("Error accessing registry:", e) return None 但可能在某些系统上,AppliedDPI不存在,此时可能需要尝试其他键,比如LogPixels。 或者,可能在HKEY_LOCAL_MACHINE下的某个位置,但普通用户可能没有权限读取。 因此,可能需要结合注册表和API方法。 另一个可能性是,对于多显示器的情况,用户可能需要每个显示器的缩放比例,但问题可能只要求主显示器的设置。 综上,最终的解决方案可能需要: 1. 使用ctypes设置DPI感知。 2. 调用GetDpiForSystem或GetDeviceCaps来获取系统DPI。 3. 计算缩放比例。 例如,使用GetDeviceCaps: import ctypes def get_scaling_factor(): try: # 尝试设置DPI感知 ctypes.windll.shcore.SetProcessDpiAwareness(1) except: # 回退到旧方法 ctypes.windll.user32.SetProcessDPIAware() hdc = ctypes.windll.user32.GetDC(None) dpi = ctypes.windll.gdi32.GetDeviceCaps(hdc, 88) # 88是LOGPIXELSX ctypes.windll.user32.ReleaseDC(None, hdc) return dpi / 96.0 这种方法可能更可靠,因为它直接查询设备的DPI,而不管注册表如何设置。不过,需要确保进程的DPI感知设置正确,否则可能得到缩放后的值。 例如,如果进程没有DPI感知,GetDeviceCaps可能返回96,而实际DPI更高,导致缩放比例计算错误。因此,设置进程的DPI感知非常重要。 因此,正确的步骤应该是: 1. 设置进程DPI感知。 2. 获取设备DPI。 3. 计算缩放比例。 这样,即使用户的缩放比例不是100%,也能正确获取实际值。 总结,Python中获取Windows实际缩放比例的方法主要有两种:通过注册表读取AppliedDPI或LogPixels,或通过Win32 API获取DPI值。考虑到不同系统版本和配置,结合两种方法可能更可靠,但需要处理异常。 现在,用户可能需要一个简单的代码示例,所以需要整合这些方法,并处理可能的错误。 最终,可能的代码示例: 方法一:使用注册表(可能适用于较新Windows版本): import winreg def get_scaling_factor_registry(): try: key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Control Panel\Desktop\WindowMetrics") applied_dpi, _ = winreg.QueryValueEx(key, "AppliedDPI") return applied_dpi / 96.0 except FileNotFoundError: # 尝试其他键 try: key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Control Panel\Desktop") log_pixels, _ = winreg.QueryValueEx(key, "LogPixels") return log_pixels / 96.0 except Exception as e: print("Registry method failed:", e) return None except Exception as e: print("Error accessing registry:", e) return None 方法二:使用Win32 API: import ctypes def get_scaling_factor_api(): try: # 设置DPI感知 ctypes.windll.shcore.SetProcessDpiAwareness(1) except: # 回退到旧API try: ctypes.windll.user32.SetProcessDPIAware() except: pass # 获取系统DPI try: dpi = ctypes.windll.user32.GetDpiForSystem() except: # 使用GetDeviceCaps作为备用 hdc = ctypes.windll.user32.GetDC(None) dpi = ctypes.windll.gdi32.GetDeviceCaps(hdc, 88) # LOGPIXELSX ctypes.windll.user32.ReleaseDC(None, hdc) return dpi / 96.0 然后,结合两种方法,优先使用API方法,因为更准确: def get_scaling_factor(): scaling_factor = get_scaling_factor_api() if scaling_factor is None or scaling_factor == 1.0: # 尝试注册表方法 scaling_factor = get_scaling_factor_registry() return scaling_factor 不过,需要处理可能的异常,比如API方法返回None或者注册表方法也失败。 此外,可能还需要考虑多显示器的情况,但用户问题可能只需要主显示器的缩放比例。 测试这个代码是否能正确返回缩放比例。例如,当缩放设置为150%时,应返回1.5。 另外,需要注意,在Python中,某些API调用可能需要管理员权限,但读取注册表的HKEY_CURRENT_USER通常不需要。 最后,给出用户一个综合的方法,说明不同方法的优缺点,并提供示例代码。</think>要在Python中获取Windows的实际缩放比例,可以通过以下方法实现: --- ### 方法1:通过Win32 API获取DPI(推荐) 使用`ctypes`调用系统API获取当前DPI值,计算缩放比例: ```python import ctypes def get_scaling_factor(): try: # 设置进程DPI感知模式(Windows 8.1+) ctypes.windll.shcore.SetProcessDpiAwareness(1) except: # 回退到旧版API(Windows Vista+) ctypes.windll.user32.SetProcessDPIAware() # 获取系统DPI try: dpi = ctypes.windll.user32.GetDpiForSystem() except: # 备用方法:通过设备上下文获取DPI hdc = ctypes.windll.user32.GetDC(None) dpi = ctypes.windll.gdi32.GetDeviceCaps(hdc, 88) # LOGPIXELSX=88 ctypes.windll.user32.ReleaseDC(None, hdc) return dpi / 96.0 print("缩放比例:", get_scaling_factor()) ``` --- ### 方法2:读取注册表值 从注册表中获取`AppliedDPI`或`LogPixels`值(可能因系统版本不同而路径不同): ```python import winreg def get_scaling_factor_registry(): try: # 尝试读取新版本Windows的AppliedDPI key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Control Panel\Desktop\WindowMetrics") applied_dpi, _ = winreg.QueryValueEx(key, "AppliedDPI") return applied_dpi / 96.0 except FileNotFoundError: # 回退到旧版LogPixels key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Control Panel\Desktop") log_pixels, _ = winreg.QueryValueEx(key, "LogPixels") return log_pixels / 96.0 except Exception as e: print("注册表读取失败:", e) return None print("缩放比例(注册表):", get_scaling_factor_registry()) ``` --- ### 方法对比 | 方法 | 优点 | 缺点 | |------------|-----------------------|------------------------------| | **Win32 API** | 实时准确,支持多显示器 | 需处理DPI感知设置 | | **注册表** | 无需额外权限 | 路径可能变化,不适用多显示器 | --- ### 常见问题处理 1. **返回值为1.0** 确保已设置DPI感知模式,否则可能获取虚拟化后的值。 2. **多显示器差异** 若需获取特定显示器的缩放比例,需调用`GetDpiForMonitor`并遍历显示器句柄。 3. **兼容性** API方法在Windows 10+更可靠,注册表方法可能过时。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值