【问题标题】:Detect if non DPI-aware application has been scaled/virtualized检测非 DPI 感知应用程序是否已被缩放/虚拟化
【发布时间】:2016-02-04 01:44:26
【问题描述】:

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

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

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

【问题讨论】:

  • 将截屏代码移动到另一个 dpiaware的应用程序中。
  • @HansPassant 这是一个很好的建议,但是我们通过远程桌面服务将这个应用程序作为 RemoteApp 提供服务,这确实使这种方法变得复杂。

标签: c# .net windows dpi


【解决方案1】:

未明确标记为支持高 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,那么您正在被虚拟化。

    这是一个快速而简单的解决方案,因为您只需要一个简单的 P/Invoke 定义就可以从 WinForms 应用程序中调用它。但是,除了布尔值“我们是否已缩放/虚拟化?”之外,您不应依赖其结果。指标。换句话说,不要相信它返回的比例因子

    在系统 DPI 为 96 且高 DPI 显示器具有 144 DPI(150% 缩放)的 Windows 10 系统上,GetScaleFactorForMonitor 函数在预期返回 SCALE_150_PERCENT 时返回 SCALE_140_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 用左、上、右和下范围来表示矩形。宽度是右范围减去左范围,而高度是底部范围减去顶部范围。 )

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

【讨论】:

  • 感谢您的详细回复!这正是我想要的!
  • 请注意,根本不应该使用“GetScaleFactorForMonitor”方法,即使只是检查是否正在应用缩放。根据我在 Windows 10 上的测试(仅检查非周年纪念日),比例因子为 100% 或 125%,此函数仍返回 SCALE_100_PERCENT,如答案中提到的更高数字返回异常值。这绝对是一个 Windows 错误。枚举值本身映射到整数,即 SCALE_150_PERCENT = 150,您希望它们这样做是为了使任何比例因子都可以在此处准确地作为整数返回。希望他们能解决它。
  • 哇,我一直在寻找类似问题的解决方案,在这里它得到了精美的解释和非常详细的解释。这个答案值得更多的支持。
  • Monitor #1 是 125%,Monitor #2 仍然是 100%,如果我们没有标记 PerMonitor DpiAwareness 但在清单文件中标记 dpiAware = true,您的代码仍然为两个监视器返回 double true。正常吗?监视器 #1:cxPhysical = 1366;cyPhysical = 768 cxLogical = 1366;cyLogical = 768 监视器 #2:cxPhysical = 1920;cyPhysical = 1080 cxLogical = 2400;cyLogical = 1350
  • 看起来逻辑是:ASSERT(cxPhysical == cxLogical && cyPhysical == cyLogical );
猜你喜欢
  • 2015-10-25
  • 2011-05-03
  • 1970-01-01
  • 2017-08-15
  • 2012-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多