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

Man*_*uel 7 .net c# windows dpi

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

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

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

Cod*_*ray 15

未明确标记为高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,那么您正在进行虚拟化.

    这是一个快速而肮脏的解决方案,因为只需要从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%.此后,缩放因子已在Windows 10中统一,但此功能似乎尚未更新,仍会为桌面应用程序返回不可靠的结果.

  2. 根据监视器的逻辑和物理宽度自行计算缩放因子.

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

    然后,您将使用此监视器句柄通过调用该GetMonitorInfo函数来获取该监视器的逻辑宽度.这填充了一个MONITORINFOEX结构,该结构包含作为其成员之一的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);
    
    Run Code Online (Sandbox Code Playgroud)

  • @CodyGray你尝试过你的功能吗?对于 Windows 10.0.19044.1889,非 DPI 感知应用程序,“GetDPIForMonitor”返回 96(而不是 120),“GetScaleFactorForMonitor”返回 100(而不是 125),“GetMonitorInfo”返回 1536x864(而不是 1920x1080)。此行为在某些 Windows 版本中是否有所改变? (2认同)