我想根据可用的屏幕 DPI 缩放我的 WPF 应用程序。我知道每个显示器 DPI 感知可用,但这与我想要实现的目标完全不同。我想禁用我的 WPF 应用程序的缩放,即使 Windows 在每个监视器或系统范围内具有一定的缩放比例。我知道如果系统缩放被禁用,那么 Windows 决定使用什么缩放因子是我的主要目标。
要在启动时根据初始 DPI 缩放 WPF 应用程序,我需要获取当前 DPI。并且窗口的当前 DPI 始终等于 WM_DPICHANGED 发送的最后一个 DPI。
这是我的代码:
public partial class MainWindow : Window
{
private HwndSource hwndSource;
public MainWindow()
{
InitializeComponent();
}
protected override void OnSourceInitialized(EventArgs e)
{
hwndSource = PresentationSource.FromVisual((Visual) sender) as HwndSource;
hwndSource.AddHook(new HwndSourceHook(WndProc));
}
private const int WM_DPICHANGED = 0x02E0;
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_DPICHANGED)
{
handled = true;
}
return IntPtr.Zero;
}
}
看看这个代码。这就是我禁用系统缩放的方法。但不利因素是,当我以不同的屏幕分辨率 (DPI) 重新启动应用程序时,未检测到初始 DPI。它在启动时没有接收到 WM_DPICHANGED,并且对初始渲染没有影响。
我只需要找到 WM_DPICHANGED 发送的最后一个 DPI 即可。如果我以某种方式跟踪它,那么我可以根据它轻松扩展我的应用程序。所以,不存在初始 DPI 问题。
我知道,我说了很多,整个问题很混乱。因此,我在这里总结一下这个问题以及我想要的答案。我把它分成两部分 -
如何追踪WM_DPICHANGED发送的Last DPI?
获取最后一个 WM_DPICHANGED 后,想要将其应用到我的 WPF 界面缩放中,以便检测到初始 DPI,并且我的应用程序自行缩放,而不是由系统/操作系统缩放。
Stack Overflow 中提供了很多基于 C# 的解决方案,但它们都不起作用,而且相当旧的解决方案在当前的 Windows 11 环境中不起作用。请尝试添加/修改我给定的基于 Win32 的代码。如果每个步骤描述正确,也欢迎其他基于纯 C# 的代码解决方案。
尝试与 WPF 争夺 DPI 控制权,您将进入一个痛苦的世界。 我建议 您遇到 X-Y 问题并且需要发布有关您实际问题的问题,或者使用正常的 WPF 支持高 DPI。
无论如何,过滤
WM_DPICHANGED
(设置 handled = true
以阻止 WPF 处理消息)不会禁用系统缩放,它会阻止 WPF 侦听 DPI 信息。 系统缩放 受 清单 <dpiAware>
元素或通过 Set*DpiAwareness*
函数(WPF 可能正在调用)的影响。 详细信息取决于您的应用程序的清单以及 WPF/.Net/dotnet 的版本以及您正在使用的 Windows。
WM_DPICHANGED
提供新的 DPI 作为参数。 具体来说,就是 wParam 的低位字。
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_DPICHANGED)
{
handled = true;
var dpi = unchecked((int)(wParam & 0xffff));
Console.WriteLine($"New DPI: {dpi}");
}
return IntPtr.Zero;
}