检测是否已缩放/虚拟化非DPI感知应用程序

时间:2015-11-03 19:01:05

标签: c# .net windows dpi

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

我们正在使应用程序具有DPI感知能力,但在此之前,我们需要一个“快速修复”,以便我们检测是否缩放。这样做的原因是它破坏了截取屏幕截图的应用程序中的功能。我们使用Graphics.CopyFromScreen中的缩放尺寸,它采用了错误尺寸的屏幕截图,因为它需要非缩放尺寸。

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

1 个答案:

答案 0 :(得分:14)

系统会欺骗未明确标记为高DPI感知的应用程序,并告知有96 DPI,缩放因子为100%。为了获得真正的DPI设置,并避免DWM自动虚拟化,您需要在应用程序的清单中包含<dpiAware>True/PM</dpiAware>。有关详细信息,请here

在您的情况下,听起来您正在寻找LogicalToPhysicalPointForPerMonitorDPIPhysicalToLogicalPointForPerMonitorDPI对功能。正如链接文档所解释的那样,默认情况下,系统将根据调用者的DPI感知返回有关其他窗口的信息。因此,如果非DPI感知应用程序尝试获取高DPI感知进程的窗口的边界,则它将获得已转换为其自己的非DPI感知坐标空间的边界。用这些函数的术语来说,这将是“逻辑”坐标。您可以将这些转换为“物理”坐标,这些坐标是操作系统(以及其他高DPI感知进程)实际使用的坐标。

回答你的实际问题:如果你绝对需要突破操作系统,那就是 DPI意识到的过程,我可以想到两种方法:

  1. 调用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,但此功能似乎尚未更新,但仍会为桌面应用程序返回不可靠的结果。

  2. 根据显示器的逻辑和物理宽度自行计算缩放系数。

    首先,当然,您需要获得HMONITOR(特定物理监视器的句柄)。您可以通过调用MonitorFromWindow,将句柄传递给WinForms窗口并指定MONITOR_DEFAULTTONEAREST来完成此操作。这将使您了解显示您感兴趣的窗口的显示器。

    然后,您将使用此监视器句柄通过调用GetMonitorInfo函数来获取该监视器的逻辑宽度。这将填充MONITORINFOEX structure,其中包含RECT结构(rcMonitor)作为其成员之一,其中包含该监视器的虚拟屏幕坐标。 (请记住,与.NET不同,Windows API根据左,上,右和底部区域表示矩形。宽度是右侧范围减去左侧范围,而高度是底部范围减去顶部范围。 )

    MONITORINFOEX填写的GetMonitorInfo结构也会为您提供该监视器的名称(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);