You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 13:30:51