检测是否不支持DPI的应用程序已被缩放/虚拟化

我试图在WinForms应用程序中检测是否由于操作系统具有较高的DPI而以缩放/虚拟化模式启动。 目前,在一个运行在3840×2400的系统中,具有200%的缩放比例,应用程序将分辨率视为1920×1200,DPI为96,比例因子为1。

我们正在使应用程序DPI知道的过程中,但在那之前,我们需要一个“快速修复”,这将允许我们检测是否缩放。 原因是它在应用程序中打破了屏幕截图的function。 我们在Graphics.CopyFromScreen中使用缩放的尺寸,因为它期望非缩放的尺寸,所以截取了错误的尺寸。

我知道DPI感知设置,但是目前,我们仍然希望应用程序被缩放,但是如果可能的话,能够检测到我们已经缩放并获得了非缩放尺寸。

没有明确标记为高DPI的应用程序将被系统欺骗,并告知96 DPI的缩放因子为100%。 为了获得真正的DPI设置,并避免DWM自动虚拟化,您需要在应用程序的清单中包含<dpiAware>True/PM</dpiAware> 。 更多信息可以在这里找到 。

在你的情况,这听起来像你正在寻找LogicalToPhysicalPointForPerMonitorDPIPhysicalToLogicalPointForPerMonitorDPI功能对。 正如链接文档所解释的那样,默认情况下,系统将根据调用者的DPI意识返回有关其他窗口的信息。 因此,如果一个不识别DPI的应用程序试图获取高DPI感知过程的窗口的界限,那么它将获得已被转换为其自己的非DPI感知坐标空间的界限。 在这些功能中,这就是“逻辑”坐标。 您可以将它们转换为“物理”坐标,这些坐标是操作系统实际使用的坐标(以及其他可识别DPI的进程)。

但是要回答你的实际问题:如果你绝对需要在不是 DPI的过程中突破操作系统的谎言,我可以想到两种方法:

  1. 调用GetScaleFactorForMonitor函数。 如果生成的DEVICE_SCALE_FACTOR值是除SCALE_100_PERCENT以外的任何值,则将缩放。 如果你的应用程序不是DPI,那么你正在被虚拟化。

    这是一个快速和肮脏的解决方案,因为一个简单的P / Invoke定义就是您需要从WinForms应用程序调用它的所有东西。 然而,除了布尔型“我们是否缩放/虚拟化?”之外,您不应该依赖其结果。 指示符。 换句话说, 不要相信它返回的比例因子

    在系统DPI为96,高DPI监视器具有144 DPI(150%缩放比例)的Windows 10系统上, GetScaleFactorForMonitor函数在返回SCALE_150_PERCENT (144/96 == 1.5)时返回SCALE_150_PERCENT 。 我不明白为什么会这样。 我唯一能想出来的是它是为Windows 8.1上的Metro / Modern / UWP应用程序设计的,其中150%不是有效的比例因子,而是140%。 在Windows 10中缩放因子已经统一 ,但是这个功能看起来并没有被更新,并且仍然为桌面应用返回不可靠的结果。

  2. 根据显示器的逻辑和物理宽度,自己计算比例因子。

    首先,当然,你需要获得一个HMONITOR (处理特定的物理监视器)。 你可以通过调用MonitorFromWindow ,传递一个句柄到你的WinForms窗口,并指定MONITOR_DEFAULTTONEAREST 。 这将使你得到一个处理你的窗口感兴趣的显示器。

    然后,您将使用此监视器句柄通过调用GetMonitorInfo函数来获取该监视器的逻辑宽度。 它填充了一个MONITORINFOEX结构 ,其中包含一个RECT结构( rcMonitor ),其中包含该监视器的虚拟屏幕坐标。 (请记住,与.NET不同的是,Windows API以左,右,上和右的范围来表示矩形,宽度是正确的范围减去左边界,而高度是底部范围减去顶部范围。 )

    GetMonitorInfo填充的MONITORINFOEX结构也会给你显示器的名字( szDevice成员)。 然后可以使用该名称来调用EnumDisplaySettings函数,该函数将用一堆关于该监视器的物理显示模式的信息填充DEVMODE结构。 您感兴趣的成员是dmPelsWidthdmPelsHeight ,它们分别给出每个宽度和高度的物理像素数。

    然后,可以将逻辑宽度除以物理宽度以确定宽度的缩放因子。 高度相同(除了我知道的所有显示器都有方形像素,所以垂直缩放系数将等于水平缩放系数)。

    在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);