From 29f9636501b0f8167cf25971724f332fdc636a54 Mon Sep 17 00:00:00 2001 From: v-leafshi Date: Fri, 6 Feb 2026 17:16:50 +0800 Subject: [PATCH] Fix incorrect ClientSize calculation in PerMonitorV2 mode when form moves between monitors with different DPI --- .../System/Windows/Forms/Form.cs | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/System.Windows.Forms/System/Windows/Forms/Form.cs b/src/System.Windows.Forms/System/Windows/Forms/Form.cs index c3900d09c6b..8746225a8b1 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Form.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Form.cs @@ -4589,8 +4589,39 @@ protected virtual bool OnGetDpiScaledSize(int deviceDpiOld, int deviceDpiNew, re SizeF currentAutoScaleDimensions = GetCurrentAutoScaleDimensions(fontwrapper.Handle); SizeF autoScaleFactor = GetCurrentAutoScaleFactor(currentAutoScaleDimensions, AutoScaleDimensions); - desiredSize.Width = (int)(Size.Width * autoScaleFactor.Width); - desiredSize.Height = (int)(Size.Height * autoScaleFactor.Height); + // We should not include the window adornments (non-client area) in the autoScaleFactor calculation, + // because Windows scales them linearly by DPI ratio. We need to: + // 1. Get the non-client area size at both old and new DPI + // 2. Subtract old non-client area from current Size to get client area + // 3. Scale client area by autoScaleFactor + // 4. Add new non-client area to get the final desired size + CreateParams cp = CreateParams; + RECT adornmentsAtOldDpi = default; + RECT adornmentsAtNewDpi = default; + + WINDOW_STYLE style = (WINDOW_STYLE)cp.Style; + WINDOW_EX_STYLE exStyle = (WINDOW_EX_STYLE)cp.ExStyle; + + if (OsVersion.IsWindows10_1703OrGreater()) + { + PInvoke.AdjustWindowRectExForDpi(ref adornmentsAtOldDpi, style, false, exStyle, (uint)deviceDpiOld); + PInvoke.AdjustWindowRectExForDpi(ref adornmentsAtNewDpi, style, false, exStyle, (uint)deviceDpiNew); + } + else + { + PInvoke.AdjustWindowRectEx(ref adornmentsAtOldDpi, style, false, exStyle); + adornmentsAtNewDpi = adornmentsAtOldDpi; + } + + // Calculate client area at old DPI + int clientWidth = Size.Width - adornmentsAtOldDpi.Width; + int clientHeight = Size.Height - adornmentsAtOldDpi.Height; + + // Scale client area by autoScaleFactor and add new DPI's non-client area. + // Use Math.Round to minimize rounding errors during DPI transitions. + desiredSize.Width = (int)Math.Round(clientWidth * autoScaleFactor.Width) + adornmentsAtNewDpi.Width; + desiredSize.Height = (int)Math.Round(clientHeight * autoScaleFactor.Height) + adornmentsAtNewDpi.Height; + Debug.WriteLine($"AutoScaleFactor computed for new DPI = {autoScaleFactor.Width} - {autoScaleFactor.Height}"); // Notify Windows that the top-level window size should be based on AutoScaleMode value.