如何使用 WinAPI 获取 Scaling-Aware Window 大小

How to Get Scaling-Aware Window Size Using WinAPI

我正在创建一个 C# WPF 应用程序,它在用户按下按钮时截取另一个程序的屏幕截图。

用户输入window想截屏的标题后,我使用WindowsAPI如下:

IntPtr window = FindWindowA(null, windowTitle);
IntPtr sourceDeviceContext = GetDC(window);
IntPtr targetDeviceContext = CreateCompatibleDC(sourceDeviceContext);
GetWindowRect(window, out RECT windowSize);
IntPtr bitmap = CreateCompatibleBitmap(
  sourceDeviceContext,
  windowSize.Right - windowSize.Left,
  windowSize.Bottom - windowSize.Top); 
SelectObject(targetDeviceContext, bitmap);
PrintWindow(window, targetDeviceContext, PrintWindowParam.PW_CLIENTONLY);
Image.FromHbitmap(bitmap).Save("C:\Image.png");

但是,如果 window 在使用缩放的屏幕上,图像最终会稍微裁剪,因为 GetWindowRect 给出的是逻辑坐标,而不是物理坐标。

我的问题是,如何找到 window 大小并考虑缩放比例?

我尝试使用 https://docs.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-getscalefactorformonitor 和以下代码找到缩放比例:

IntPtr monitor = MonitorFromWindow(window, MonitorFromWindowParam.MONITOR_DEFAULTTONEAREST);
IntPtr result = GetScaleFactorForMonitor(monitor, out DeviceScaleFactor scale);

我尝试在两台显示器之间移动应用程序(一台缩放比例为 100%,一台缩放比例为 125%),IntPtr monitor 值发生适当变化,因此可以正确检测到显示器。此外,IntPtr resultS_OK,意味着函数成功。但是,它总是给我 100% 的缩放比例作为响应(scale 的整数值为 100),无论我得到哪个显示器的缩放比例。

是否有可靠的方法来捕获应用程序的整个 window 区域,同时考虑缩放?

感谢 Simon Mourier 对这个问题的评论,我设法通过在应用程序本身中启用 DPI 感知来解决这个问题。

完成者:

  1. 通过将此属性添加到程序集来禁用默认的 WPF DPI 感知:
[assembly: DisableDpiAwareness]
  1. 使用 P/Invoke 启用 DPI 感知:

我有一个导入这些 p/invoke 函数的 Win32Util:

public enum PROCESS_DPI_AWARENESS
{
    Process_DPI_Unaware = 0,
    Process_System_DPI_Aware = 1,
    Process_Per_Monitor_DPI_Aware = 2
}

public enum DPI_AWARENESS_CONTEXT
{
    DPI_AWARENESS_CONTEXT_UNAWARE = 16,
    DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = 17,
    DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = 18,
    DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 34
}

public static class Win32Util
{
    [DllImport("User32.dll")]
    public static extern bool SetProcessDpiAwarenessContext(int dpiFlag);

    [DllImport("SHCore.dll")]
    public static extern bool SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness);

    [DllImport("User32.dll")]
    public static extern bool SetProcessDPIAware();
}

然后我使用这些在我的应用程序执行开始时像这样启用 DPI 感知:

// App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
    DpiAwareness.Enable();
    base.OnStartup(e);
}
public static class DpiAwareness
{
    public static void Enable()
    {
        // Windows 8.1 added support for per monitor DPI
        if (Environment.OSVersion.Version >= new Version(6, 3, 0))
        {
            // Windows 10 creators update added support for per monitor v2
            if (Environment.OSVersion.Version >= new Version(10, 0, 15063))
            {
                Win32Util.SetProcessDpiAwarenessContext((int)DPI_AWARENESS_CONTEXT.DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
            }
            else
            {
                Win32Util.SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.Process_Per_Monitor_DPI_Aware);
            }
        }
        else
        {
            Win32Util.SetProcessDPIAware();
        }
    }
}