多显示器不同缩放比例下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
相关产品推荐
相关产品推荐

