我试图在WinForms应用程序中检测是否由于操作系统具有高DPI而以缩放/虚拟化模式启动。目前,在运行速度为3840x2400且缩放率为200%的系统中,应用程序将分辨率视为1920x1200,DPI为96,比例因子为1.
我们正在使应用程序具有DPI感知能力,但在此之前,我们需要一个“快速修复”,以便我们检测是否缩放。这样做的原因是它破坏了截取屏幕截图的应用程序中的功能。我们使用Graphics.CopyFromScreen中的缩放尺寸,它采用了错误尺寸的屏幕截图,因为它需要非缩放尺寸。
我知道DPI感知设置,但目前我们仍然希望缩放应用程序,但是如果可能的话,能够检测到我们是否已缩放并获得非缩放尺寸。
答案 0 :(得分:14)
系统会欺骗未明确标记为高DPI感知的应用程序,并告知有96 DPI,缩放因子为100%。为了获得真正的DPI设置,并避免DWM自动虚拟化,您需要在应用程序的清单中包含<dpiAware>True/PM</dpiAware>
。有关详细信息,请here。
在您的情况下,听起来您正在寻找LogicalToPhysicalPointForPerMonitorDPI
和PhysicalToLogicalPointForPerMonitorDPI
对功能。正如链接文档所解释的那样,默认情况下,系统将根据调用者的DPI感知返回有关其他窗口的信息。因此,如果非DPI感知应用程序尝试获取高DPI感知进程的窗口的边界,则它将获得已转换为其自己的非DPI感知坐标空间的边界。用这些函数的术语来说,这将是“逻辑”坐标。您可以将这些转换为“物理”坐标,这些坐标是操作系统(以及其他高DPI感知进程)实际使用的坐标。
回答你的实际问题:如果你绝对需要突破操作系统,那就是不 DPI意识到的过程,我可以想到两种方法:
调用GetScaleFactorForMonitor
函数。如果生成的DEVICE_SCALE_FACTOR
值不是SCALE_100_PERCENT
,则缩放。如果您的应用程序不支持DPI,那么您正在进行虚拟化。
这是一个快速而肮脏的解决方案,因为您需要从WinForms应用程序中调用它,只需要一个简单的P / Invoke定义。但是,你不应该依赖它的结果而不是布尔“我们是否缩放/虚拟化?”指示符。换句话说,不信任它返回的比例因子!
在系统DPI为96且高DPI监视器具有144 DPI(150%缩放比例)的Windows 10系统上,GetScaleFactorForMonitor
函数返回SCALE_140_PERCENT
时预期返回SCALE_150_PERCENT
(144/96 == 1.5)。我真的不明白为什么会这样。我唯一可以理解的是它是为Windows 8.1上的Metro / Modern / UWP应用程序设计的,其中150%不是有效的比例因子,而是140%。此后缩放因子为unified in Windows 10,但此功能似乎尚未更新,但仍会为桌面应用程序返回不可靠的结果。
根据显示器的逻辑和物理宽度自行计算缩放系数。
首先,当然,您需要获得HMONITOR
(特定物理监视器的句柄)。您可以通过调用MonitorFromWindow
,将句柄传递给WinForms窗口并指定MONITOR_DEFAULTTONEAREST
来完成此操作。这将使您了解显示您感兴趣的窗口的显示器。
然后,您将使用此监视器句柄通过调用GetMonitorInfo
函数来获取该监视器的逻辑宽度。这将填充MONITORINFOEX
structure,其中包含RECT
结构(rcMonitor
)作为其成员之一,其中包含该监视器的虚拟屏幕坐标。 (请记住,与.NET不同,Windows API根据左,上,右和底部区域表示矩形。宽度是右侧范围减去左侧范围,而高度是底部范围减去顶部范围。 )
MONITORINFOEX
填写的GetMonitorInfo
结构也会为您提供该监视器的名称(szDevice
成员)。然后,您可以使用该名称来调用EnumDisplaySettings
函数,该函数将填充DEVMODE
结构,其中包含有关该监视器的物理显示模式的大量信息。您感兴趣的成员是dmPelsWidth
和dmPelsHeight
,它们分别为您提供每个宽度和高度的物理像素数。
然后,您可以将逻辑宽度除以物理宽度,以确定宽度的缩放系数。高度相同(除了我知道的所有显示器都有方形像素,因此垂直缩放系数将等于水平缩放系数)。
示例代码,在Windows 10中测试和使用(用C ++编写,因为这是我的方便;对不起,你必须自己翻译成.NET):
// Get the monitor that the window is currently displayed on
// (where hWnd is a handle to the window of interest).
HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
// Get the logical width and height of the monitor.
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);
// Get the physical width and height of the monitor.
DEVMODE dm;
dm.dmSize = sizeof(dm);
dm.dmDriverExtra = 0;
EnumDisplaySettings(miex.szDevice, ENUM_CURRENT_SETTINGS, &dm);
int cxPhysical = dm.dmPelsWidth;
int cyPhysical = dm.dmPelsHeight;
// Calculate the scaling factor.
double horzScale = ((double)cxPhysical / (double)cxLogical);
double vertScale = ((double)cyPhysical / (double)cyLogical);
ASSERT(horzScale == vertScale);