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

多显示器不同缩放比例下AppWindow MoveAndResize DPI窗口尺寸异常

跨DPI显示器切换时AppWindow尺寸异常问题解决

问题背景

两台显示器缩放比例分别为125%和150%,使用AppWindow.MoveAndResize配合DisplayArea实现跨屏窗口显示时,首次切换到目标屏幕窗口尺寸异常,同一屏幕二次操作后恢复正常;切回主显示器也存在相同问题。即使按目标屏幕DPI缩放窗口尺寸,首次切换仍异常。

问题原因

这是WinUI 3中AppWindow跨DPI显示器切换时的已知行为:首次将窗口移动到新DPI屏幕时,窗口的DPI感知未完成更新,导致传入的物理像素尺寸被重复缩放,进而出现尺寸异常。第二次操作时,窗口已适配目标屏幕的DPI,尺寸恢复正常。

解决方案

1. 切换屏幕时先隐藏窗口再重新显示

移动窗口到新屏幕前先隐藏窗口,完成尺寸调整后再显示,强制窗口在新DPI环境下重新初始化尺寸计算。

修改MoveWindow方法如下:

private void MoveWindow(bool isSameScreen = false)
{
    if (!isSameScreen)
    {
        _index++;
    }
    
    DisplayArea area = _displayAreas[_index % 2];

    // 获取目标屏幕的DPI缩放比例
    IntPtr hMonitor = Win32Interop.GetMonitorFromDisplayId(area.DisplayId);
    GetDpiForMonitor(hMonitor, Windows.Win32.UI.HiDpi.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out uint dpiX, out uint dpiY);
    double scaleFactor = dpiX / 96.0;

    // 将设备独立尺寸转换为物理像素
    int physicalWidth = (int)Math.Round(_wndWidth * scaleFactor);
    int physicalHeight = (int)Math.Round(_wndHeight * scaleFactor);

    Windows.Graphics.RectInt32 rect = new()
    {
        X = (area.WorkArea.Width - physicalWidth) / 2,
        Y = (area.WorkArea.Height / 3) - (physicalHeight / 2),
        Height = physicalHeight,
        Width = physicalWidth
    };

    Debug.WriteLine($"Window position: [X: {rect.X}, Y: {rect.Y}, W: {rect.Width}, H: { rect.Height}]");
    
    // 先隐藏窗口,确保DPI环境重置
    _appWindow.Hide();
    // 移动并调整尺寸
    _appWindow.MoveAndResize(rect, area);
    // 重新显示窗口
    _appWindow.Show();
    _appWindow.MoveInZOrderAtTop();
}

2. 确认DPI感知配置正确

确保app.manifest中的DPI感知设置为PerMonitorV2,该配置已在你的代码中正确设置,需保证无其他冲突的DPI配置:

<application xmlns="urn:schemas-microsoft-com:asm.v3">
    <windowsSettings>
      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
    </windowsSettings>
</application>

3. 监听DPI变化动态调整(可选)

若需更动态的适配,可监听窗口所在DisplayArea的DPI变化事件,在DPI改变时重新调整窗口尺寸:

// 在窗口初始化方法中添加监听
public MainWindow()
{
    this.InitializeComponent();
    
    _appWindow = GetAppWindowForCurrentWindow();
    _appWindow.SetPresenter(AppWindowPresenterKind.CompactOverlay);
    // 新增DPI变化监听
    _appWindow.Changed += AppWindow_Changed;

    InitDisplayArea();
    MoveWindow();
}

private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
{
    if (args.DpiChanged)
    {
        DisplayArea currentArea = DisplayArea.GetFromWindowId(sender.Id, DisplayAreaFallback.Nearest);
        IntPtr hMonitor = Win32Interop.GetMonitorFromDisplayId(currentArea.DisplayId);
        GetDpiForMonitor(hMonitor, Windows.Win32.UI.HiDpi.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out uint dpiX, out uint dpiY);
        double scaleFactor = dpiX / 96.0;

        int physicalWidth = (int)Math.Round(_wndWidth * scaleFactor);
        int physicalHeight = (int)Math.Round(_wndHeight * scaleFactor);

        Windows.Graphics.RectInt32 rect = new()
        {
            X = (currentArea.WorkArea.Width - physicalWidth) / 2,
            Y = (currentArea.WorkArea.Height / 3) - (physicalHeight / 2),
            Height = physicalHeight,
            Width = physicalWidth
        };

        sender.MoveAndResize(rect, currentArea);
    }
}

关键说明

  • AppWindow.MoveAndResize接收的是物理像素尺寸,必须将设备独立值(如_wndWidth、_wndHeight)按目标屏幕的DPI缩放比例转换为物理像素后传入。
  • 切换屏幕时先隐藏窗口,可避免DPI适配延迟导致的首次尺寸异常问题。

内容的提问来源于stack exchange,提问作者phiwhynot

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 15:47:45