首页 > 解决方案 > 如何使用 WinAPI 获取缩放感知窗口大小

问题描述

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

用户输入他们想要截屏的窗口标题后,我使用 Windows API 如下:

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

但是,如果窗口位于使用缩放的屏幕上,则图像最终会略微裁剪,因为 GetWindowRect 提供的是逻辑坐标,而不是物理坐标。

我的问题是,我怎样才能找到窗口大小并考虑缩放?

我尝试使用以下代码找到缩放比例

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

我尝试在两台显示器(一台为 100%,另一台为 125% 缩放)之间移动应用程序,并且IntPtr monitor值会相应更改,因此可以正确检测到监视器。此外,IntPtr resultis S_OK,表示函数成功。但是,它总是给我 100% 的缩放作为响应(的整数值scaleis 100),无论我得到哪个监视器的缩放。

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

标签: c#wpfwinapi

解决方案


感谢 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();
        }
    }
}

推荐阅读