检测不支持DPI的应用程序是否已扩展/虚拟化 [英] Detect if non DPI-aware application has been scaled/virtualized

查看:151
本文介绍了检测不支持DPI的应用程序是否已扩展/虚拟化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to detect in a WinForms application if it has been launched in scaled/virtualized mode due to the OS having a high DPI. Currently, in a system running at 3840x2400 with 200% scaling, the application sees the resolution as 1920x1200, the DPI as 96, and the scale factor is 1.

在使应用程序能够感知DPI的过程中,但是直到那时,我们需要一个快速修复工具,使我们能够检测是否已缩放。这样做的原因是它破坏了需要截图的应用程序的功能。我们在Graphics.CopyFromScreen中使用了比例尺尺寸,它会截取错误尺寸的屏幕截图,因为它期望使用非比例尺尺寸。

We are in the process of making the application DPI-aware, but until then, we need a "quick fix" that will allow us to detect if scaled. The reason for this is that it breaks a functionality in the application that takes a screenshot. We use the scaled dimensions in Graphics.CopyFromScreen, it takes a screenshot of the wrong size since it is expecting the non-scaled dimensions.

我知道DPI-感知设置,但就目前而言,我们仍然希望对应用程序进行缩放,但能够检测到我们是否已缩放并获得未缩放的尺寸。

I am aware of the DPI-awareness setting, but for the moment, we still want the application to be scaled, but be able to detect that we are scaled and get the non-scaled dimensions, if possible.

推荐答案

系统将谎称未明确标记为具有高DPI意识的应用程序,并告知存在96个DPI,缩放比例为100%。为了获得真实的DPI设置,并避免DWM自动进行虚拟化,您需要在应用程序清单中包括< dpiAware> True / PM< / dpiAware> 。提供了更多信息这里

An application that is not explicitly marked as high-DPI aware will be lied to by the system and told that there are 96 DPI with a scaling factor of 100%. In order to get the real DPI settings, and avoid automatic virtualization by DWM, you will need to include <dpiAware>True/PM</dpiAware> in your application's manifest. More information is available here.

在您的情况下,听起来您正在寻找 LogicalToPhysicalPointForPerMonitorDPI PhysicalToLogicalPointForPerMonitorDPI 对功能。如链接文档所述,默认情况下,系统将基于呼叫者的DPI感知返回有关其他窗口的信息。因此,如果非DPI感知的应用程序尝试获取高DPI感知进程的窗口的边界,它将获取已转换为自己的非DPI感知坐标空间的边界。就这些功能而言,这就是逻辑坐标。您可以将它们转换为物理坐标,这些坐标是操作系统(和其他具有DPI高度感知的进程)实际使用的坐标。

In your case, it sounds like you are looking for the LogicalToPhysicalPointForPerMonitorDPI and PhysicalToLogicalPointForPerMonitorDPI pair of functions. As the linked documentation explains, by default, the system will return information about other windows based on the DPI awareness of the caller. So if a non-DPI aware application tries to get the bounds of a window of a high-DPI aware process, it will get bounds that have been translated into its own non-DPI aware coordinate space. This would be, in the vernacular of these functions, the "logical" coordinates. You can convert these to "physical" coordinates, which are those that are actually used by the operating system (and other high-DPI aware processes).

要回答您的实际问题,如果:如果您绝对需要突破操作系统的谎言,而该进程是 DPI不能识别的,我可以想到两种方法:

To answer your actual question, though: If you absolutely need to break through the operating system's lies in a process that is not DPI aware, I can think of two ways to do so:


  1. 呼叫 GetScaleFactorForMonitor 函数。如果得到的 DEVICE_SCALE_FACTOR 值是 SCALE_100_PERCENT 以外的任何其他值,然后进行缩放。如果您的应用程序不支持DPI,则说明您已进行了虚拟化。

  1. Call the GetScaleFactorForMonitor function. If the resulting DEVICE_SCALE_FACTOR value is anything other than SCALE_100_PERCENT, then you are scaled. If your application is not DPI aware, then you are being virtualized.

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

This is a quick-and-dirty solution, as a simple P/Invoke definition is all you need to call it from a WinForms application. However, you should not rely on its results for anything more than a Boolean "are we scaled/virtualized?" indicator. In other words, do not trust the scale factor that it returns!

在Windows 10系统上,系统DPI为96,并且高DPI监视器具有144 DPI(缩放比例为150%), GetScaleFactorForMonitor 函数将在预期时返回 SCALE_140_PERCENT 返回 SCALE_150_PERCENT (144/96 == 1.5)。我真的不明白为什么会这样。我唯一能弄清楚的是它是为Windows 8.1上的Metro / Modern / UWP应用程序设计的,其中150%不是有效比例因子,而140%是有效比例因子。此后,缩放因子为在Windows 10中已经统一,但是此功能似乎尚未更新,仍然对于桌面应用程序返回不可靠的结果。

On a Windows 10 system where the system DPI is 96, and a high-DPI monitor has a 144 DPI (150% scaling), the GetScaleFactorForMonitor function returns SCALE_140_PERCENT when it would be expected to return SCALE_150_PERCENT (144/96 == 1.5). I don't really understand why this is the case. The only thing I can figure out is that it was designed for Metro/Modern/UWP apps on Windows 8.1, where 150% is not a valid scale factor but 140% is. The scaling factors have since been unified in Windows 10, but this function appears not to have been updated and still returns unreliable results for desktop applications.

计算

当然,首先,您需要获取 HMONITOR (处理到特定的物理监视器)。您可以通过调用 MonitorFromWindow ,将句柄传递到WinForms窗口,并指定 MONITOR_DEFAULTTONEAREST

First, of course, you'll need to obtain an HMONITOR (handle to a specific physical monitor). You can do this by calling MonitorFromWindow, passing a handle to your WinForms window, and specifying MONITOR_DEFAULTTONEAREST. That will get you a handle to the monitor that your window of interest is being displayed on.

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

Then, you'll use this monitor handle to get the logical width of that monitor by calling the GetMonitorInfo function. That fills in a MONITORINFOEX structure that contains, as one of its members, a RECT structure (rcMonitor) that contains the virtual-screen coordinates of that monitor. (Remember that, unlike .NET, the Windows API represents rectangles in terms of their left, top, right, and bottom extents. The width is the right extent minus the left extent, while the height is the bottom extent minus the top extent.)

GetMonitorInfo 填充的 MONITORINFOEX 结构将还为您提供了该监视器的名称( szDevice 成员)。然后,您可以使用该名称来调用 EnumDisplaySettings 函数,该函数将在 DEVMODE 结构中填充有关该监视器的物理显示模式的大量信息。您感兴趣的成员是 dmPelsWidth dmPelsHeight ,它们为您提供每个宽度和高度的物理像素数

The MONITORINFOEX structure filled in by GetMonitorInfo will also have given you the name of that monitor (the szDevice member). You can then use that name to call the EnumDisplaySettings function, which will fill in a DEVMODE structure with a bunch of information about the physical display modes for that monitor. The members you're interested in are dmPelsWidth and dmPelsHeight, which give you the number of physical pixels per width and height, respectively.

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

You can then divide the logical width by the physical width to determine the scaling factor for the width. Same thing for the height (except that all monitors I'm aware of have square pixels, so the vertical scaling factor will be equal to the horizontal scaling factor).

已在Windows 10中测试并运行的示例代码(用C ++编写,因为这是我很方便的东西;对不起,您必须自己翻译成.NET):

Example code, tested and working in Windows 10 (written in C++ because that's what I have handy; sorry you'll have to do your own translation to .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);


这篇关于检测不支持DPI的应用程序是否已扩展/虚拟化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆