c# - 如何使用 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 result
is S_OK
,表示函数成功。但是,它总是给我 100% 的缩放作为响应(的整数值scale
is 100
),无论我得到哪个监视器的缩放。
考虑到缩放,是否有可靠的方法来捕获应用程序的整个窗口区域?
解决方案
感谢 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();
}
}
}
推荐阅读
- angular - 部署期间的 Angular 11 构建问题
- ruby-on-rails - RSpec 控制器测试无法获取 id 参数。我不知道如何解决这个问题
- c++ - “main.cpp”中的切换正在跳过具有“stack.cpp”功能的案例
- android - 如何在Android中实现与缩放手势手指动作协调的RecyclerView GridLayoutManager spanCount变化动画?
- linux - bash -c 或 zsh -c 对它执行的字符串有限制吗?
- logging - MS FILETIME 作为数据记录的绝对时间戳似乎是一个不错的选择
- c# - 如何解决不同类型的服务的 IEnumerable
- mysql - 取回子查询内列表中的匹配值
- python - Django - 将新模型与他们自己的页面结合起来
- reactjs - 更新 MapComponent 状态时的性能问题