如何让WPF应用在任意显示器上始终禁用DPI缩放,固定物理像素尺寸?
实现WPF应用固定物理像素尺寸的方案
可以实现,无需转用Linux。核心是通过进程级DPI感知配置+手动控制窗口物理尺寸达成需求,以下是具体步骤:
1. 配置应用清单启用Per Monitor V2 DPI感知
这是最关键的一步(必须在进程启动前生效),修改项目中的app.manifest文件:
- 若项目无该文件,右键项目→添加→新建项→选择「应用程序清单文件」
- 在
<application>节点内添加如下配置:
<application xmlns="urn:schemas-microsoft-com:asm.v3"> <windowsSettings> <!-- 启用Per Monitor DPI感知 --> <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware> <!-- 优先使用PerMonitorV2,降级到unaware模式 --> <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, unaware</dpiAwareness> </windowsSettings> </application>
该配置让应用能感知各显示器的DPI差异,避免系统自动缩放窗口导致模糊,同时为手动控制尺寸提供基础。
2. 禁用WPF自动DPI缩放
在项目文件(.csproj)中添加以下设置,关闭WPF基于DPI的自动缩放逻辑:
<PropertyGroup> <DoNotScaleForDpiChanges>true</DoNotScaleForDpiChanges> </PropertyGroup>
3. 手动固定窗口物理像素尺寸
要让窗口始终保持目标物理像素(如500px宽),可通过两种方式实现:
方法一:基于DPI转换逻辑尺寸
在窗口SourceInitialized事件中(窗口句柄创建后触发,能准确获取当前显示器DPI)添加代码:
using System.Windows; using System.Windows.Interop; using Windows.Graphics.Display; public MainWindow() { InitializeComponent(); SourceInitialized += MainWindow_SourceInitialized; } private void MainWindow_SourceInitialized(object sender, EventArgs e) { var windowHandle = new WindowInteropHelper(this).Handle; var displayInfo = DisplayInformation.GetForWindowId(Win32Interop.GetWindowIdFromWindow(windowHandle)); // 获取当前显示器的DPI缩放因子(如125%缩放对应1.25) double scalingFactor = displayInfo.RawPixelsPerViewPixel; // 目标物理像素宽度 int targetPhysicalWidth = 500; // 转换为WPF逻辑像素并设置 Width = targetPhysicalWidth / scalingFactor; // 同理可设置高度:Height = targetPhysicalHeight / scalingFactor; }
方法二:直接调用Windows API设置物理尺寸
绕过WPF逻辑像素转换,直接用SetWindowPos API指定物理像素尺寸:
using System; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; public MainWindow() { InitializeComponent(); SourceInitialized += MainWindow_SourceInitialized; } [DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags); private const uint SWP_NOMOVE = 0x0002; private const uint SWP_NOZORDER = 0x0004; private void MainWindow_SourceInitialized(object sender, EventArgs e) { var windowHandle = new WindowInteropHelper(this).Handle; // 目标物理像素宽高 int targetWidth = 500; int targetHeight = 300; // 设置窗口物理尺寸,不改变位置和层级 SetWindowPos(windowHandle, IntPtr.Zero, 0, 0, targetWidth, targetHeight, SWP_NOMOVE | SWP_NOZORDER); }
4. 处理跨显示器DPI切换
若窗口被拖到不同DPI的显示器上,需监听DPI变化事件并重新调整尺寸:
using System.Windows; using System.Windows.Interop; public MainWindow() { InitializeComponent(); SourceInitialized += MainWindow_SourceInitialized; } private void MainWindow_SourceInitialized(object sender, EventArgs e) { var windowHandle = new WindowInteropHelper(this).Handle; HwndSource source = HwndSource.FromHwnd(windowHandle); source.AddHook(WndProc); } private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { const int WM_DPICHANGED = 0x02E0; if (msg == WM_DPICHANGED) { // 获取新的DPI值并计算缩放因子 int newDpi = wParam.ToInt32() & 0xFFFF; double scalingFactor = newDpi / 96.0; // 重新设置目标物理宽度对应的逻辑尺寸 Width = 500 / scalingFactor; handled = true; } return IntPtr.Zero; }
排查注意事项
- 务必恢复可执行文件兼容性中的「高DPI缩放覆盖」为默认设置,否则会覆盖清单的DPI配置。
- 确保
app.manifest的生成操作设置为「嵌入的资源」。 - 修改配置后需重启应用,DPI感知为进程级配置,重启后才会生效。
内容的提问来源于stack exchange,提问作者user1969903
相关产品推荐
相关产品推荐

