如何使用 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 result
是 S_OK
,意味着函数成功。但是,它总是给我 100% 的缩放比例作为响应(scale
的整数值为 100
),无论我得到哪个显示器的缩放比例。
是否有可靠的方法来捕获应用程序的整个 window 区域,同时考虑缩放?
感谢 Simon Mourier 对这个问题的评论,我设法通过在应用程序本身中启用 DPI 感知来解决这个问题。
完成者:
- 通过将此属性添加到程序集来禁用默认的 WPF DPI 感知:
[assembly: DisableDpiAwareness]
- 使用 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();
}
}
}
我正在创建一个 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 result
是 S_OK
,意味着函数成功。但是,它总是给我 100% 的缩放比例作为响应(scale
的整数值为 100
),无论我得到哪个显示器的缩放比例。
是否有可靠的方法来捕获应用程序的整个 window 区域,同时考虑缩放?
感谢 Simon Mourier 对这个问题的评论,我设法通过在应用程序本身中启用 DPI 感知来解决这个问题。
完成者:
- 通过将此属性添加到程序集来禁用默认的 WPF DPI 感知:
[assembly: DisableDpiAwareness]
- 使用 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();
}
}
}