From 48128f15e1f2abf1451123d7026c75ba96f078af Mon Sep 17 00:00:00 2001 From: Fahmi Fuzi Date: Tue, 16 Jun 2026 03:16:44 +1000 Subject: [PATCH] WI01087563 Fix high DPI anchor layout regression --- .../AnchorLayoutHighDpiRegressionTests.cs | 53 ++++++++++++ .../DefaultLayout.AnchorLayoutCompat.cs | 82 ++++++++++++++++++- 2 files changed, 131 insertions(+), 4 deletions(-) diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/Layout/AnchorLayoutHighDpiRegressionTests.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/Layout/AnchorLayoutHighDpiRegressionTests.cs index b2d9ce0c799..024050e8d28 100644 --- a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/Layout/AnchorLayoutHighDpiRegressionTests.cs +++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/Layout/AnchorLayoutHighDpiRegressionTests.cs @@ -548,6 +548,59 @@ public void StretchAnchoredControl_WithHealthyNegativeTrailingAnchor_StretchesWi Assert.Equal(new Rectangle(7, 175, 1511, 217), stretchControl.Bounds); } + [StaFact] + public void StretchAnchoredControl_WithNegativeTrailingAnchorCapturedAgainstTransientSmallerParent_RefreshesFromSpecifiedBounds() + { + using StretchTestContainer parent = new() + { + Bounds = new Rectangle(0, 0, 1515, 448), + SimulatedDisplayRectangle = new Rectangle(0, 0, 1515, 448) + }; + using Panel stretchControl = new() + { + Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right, + Bounds = new Rectangle(0, 0, 669, 448) + }; + using GroupBox trailingSibling = new() + { + Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right, + Bounds = new Rectangle(1142, 0, 373, 448) + }; + + parent.Controls.Add(stretchControl); + parent.Controls.Add(trailingSibling); + + // TR CargoWise trace shape: the stretch control's V1 anchor metadata was captured while + // the parent was transiently narrower than its design-time display rectangle. Projecting + // that negative trailing anchor into the final wider parent makes the control too wide. + SetSpecifiedBounds(parent, new Rectangle(0, 0, 1039, 448)); + SetSpecifiedBounds(stretchControl, new Rectangle(0, 0, 669, 448)); + SetSpecifiedBounds(trailingSibling, new Rectangle(679, 0, 373, 448)); + + DefaultLayout.SetAnchorInfo( + stretchControl, + CreateAnchorInfo( + left: 0, + top: 0, + right: -211, + bottom: 0, + displayRectangle: new Rectangle(0, 0, 880, 448))); + + parent.PerformLayout(); + + DefaultLayout.AnchorInfo refreshedAnchorInfo = DefaultLayout.GetAnchorInfo(stretchControl)!; + + Assert.Equal(-370, refreshedAnchorInfo.Right); + + parent.PerformLayout(); + + Assert.Equal(new Rectangle(0, 0, 1145, 448), stretchControl.Bounds); + Assert.True( + stretchControl.Right <= trailingSibling.Left + 3, + $"Stretch control should no longer project to the stale transient width. " + + $"StretchControl={stretchControl.Bounds}, TrailingSibling={trailingSibling.Bounds}"); + } + [StaFact] public void StretchAnchoredControl_WithStaleSpecifiedSizeAndHealthyAnchors_DoesNotRefreshAnchors() { diff --git a/src/System.Windows.Forms/System/Windows/Forms/Layout/DefaultLayout.AnchorLayoutCompat.cs b/src/System.Windows.Forms/System/Windows/Forms/Layout/DefaultLayout.AnchorLayoutCompat.cs index e4e030ff3c2..1d5b7381eab 100644 --- a/src/System.Windows.Forms/System/Windows/Forms/Layout/DefaultLayout.AnchorLayoutCompat.cs +++ b/src/System.Windows.Forms/System/Windows/Forms/Layout/DefaultLayout.AnchorLayoutCompat.cs @@ -66,11 +66,12 @@ private static bool ShouldRefreshAnchorInfoForStaleStretchAnchors( { useSpecifiedDisplayRectangleForStretchRefresh = false; - if (!ScaleHelper.IsScalingRequirementMet || element.Container is not { } container) + if (element.Container is not { } container) { return false; } + bool isScalingRequirementMet = ScaleHelper.IsScalingRequirementMet; Rectangle specifiedContainerBounds = CommonProperties.GetSpecifiedBounds(container); Rectangle specifiedElementBounds = CommonProperties.GetSpecifiedBounds(element); @@ -94,13 +95,15 @@ private static bool ShouldRefreshAnchorInfoForStaleStretchAnchors( specifiedElementBounds.Height); if (hasDisplayRectangleGrowth - && IsSpecifiedDisplayRectangleLikelyStale(specifiedDisplayRect, anchorInfo.DisplayRectangle)) + && IsSpecifiedDisplayRectangleLikelyStale(specifiedDisplayRect, anchorInfo.DisplayRectangle) + && IsSpecifiedDisplayRectangleLikelyStale(specifiedDisplayRect, displayRect)) { return false; } bool hasStaleHorizontalStretchAnchor = IsAnchored(anchor, AnchorStyles.Left) && IsAnchored(anchor, AnchorStyles.Right) + && isScalingRequirementMet && anchorInfo.Right < 0 && hasDisplayRectangleGrowth && IsProjectedStretchAnchorSmallerThanExpected( @@ -112,6 +115,7 @@ private static bool ShouldRefreshAnchorInfoForStaleStretchAnchors( bool hasStaleVerticalStretchAnchor = IsAnchored(anchor, AnchorStyles.Top) && IsAnchored(anchor, AnchorStyles.Bottom) + && isScalingRequirementMet && anchorInfo.Bottom < 0 && hasDisplayRectangleGrowth && IsProjectedStretchAnchorSmallerThanExpected( @@ -121,16 +125,55 @@ private static bool ShouldRefreshAnchorInfoForStaleStretchAnchors( effectiveSpecifiedHeight, specifiedDisplayRect.Height); - bool hasOversizedHorizontalStretchAnchor = IsAnchored(anchor, AnchorStyles.Left) + bool hasOversizedHorizontalStretchAnchorWithPositiveTrailingOffset = IsAnchored(anchor, AnchorStyles.Left) && IsAnchored(anchor, AnchorStyles.Right) + && isScalingRequirementMet && anchorInfo.Right > 0 && IsLargerThanExpectedStretchedSize(bounds.Width, specifiedElementBounds.Width, displayRect.Width, specifiedDisplayRect.Width); - bool hasOversizedVerticalStretchAnchor = IsAnchored(anchor, AnchorStyles.Top) + bool hasOversizedVerticalStretchAnchorWithPositiveTrailingOffset = IsAnchored(anchor, AnchorStyles.Top) && IsAnchored(anchor, AnchorStyles.Bottom) + && isScalingRequirementMet && anchorInfo.Bottom > 0 && IsLargerThanExpectedStretchedSize(bounds.Height, specifiedElementBounds.Height, displayRect.Height, specifiedDisplayRect.Height); + bool hasOversizedHorizontalStretchAnchorWithNegativeTrailingOffset = IsAnchored(anchor, AnchorStyles.Left) + && IsAnchored(anchor, AnchorStyles.Right) + && anchorInfo.Right < 0 + && hasDisplayRectangleGrowth + && IsSpecifiedDisplaySizeBetweenOriginalAndCurrent( + specifiedDisplayRect.Width, + anchorInfo.DisplayRectangle.Width, + displayRect.Width) + && IsProjectedStretchAnchorLargerThanExpected( + displayRect.Width, + anchorInfo.Left, + anchorInfo.Right, + effectiveSpecifiedWidth, + specifiedDisplayRect.Width); + + bool hasOversizedVerticalStretchAnchorWithNegativeTrailingOffset = IsAnchored(anchor, AnchorStyles.Top) + && IsAnchored(anchor, AnchorStyles.Bottom) + && anchorInfo.Bottom < 0 + && hasDisplayRectangleGrowth + && IsSpecifiedDisplaySizeBetweenOriginalAndCurrent( + specifiedDisplayRect.Height, + anchorInfo.DisplayRectangle.Height, + displayRect.Height) + && IsProjectedStretchAnchorLargerThanExpected( + displayRect.Height, + anchorInfo.Top, + anchorInfo.Bottom, + effectiveSpecifiedHeight, + specifiedDisplayRect.Height); + + bool hasOversizedHorizontalStretchAnchor = + hasOversizedHorizontalStretchAnchorWithPositiveTrailingOffset + || hasOversizedHorizontalStretchAnchorWithNegativeTrailingOffset; + bool hasOversizedVerticalStretchAnchor = + hasOversizedVerticalStretchAnchorWithPositiveTrailingOffset + || hasOversizedVerticalStretchAnchorWithNegativeTrailingOffset; + useSpecifiedDisplayRectangleForStretchRefresh = hasOversizedHorizontalStretchAnchor || hasOversizedVerticalStretchAnchor; return hasStaleHorizontalStretchAnchor @@ -284,6 +327,37 @@ private static bool IsProjectedStretchAnchorSmallerThanExpected( return IsSmallerThanExpectedStretchedSize(projectedSize, specifiedSize, currentDisplaySize, specifiedDisplaySize); } + private static bool IsProjectedStretchAnchorLargerThanExpected( + int currentDisplaySize, + int leadingAnchor, + int trailingAnchor, + int specifiedSize, + int specifiedDisplaySize) + { + if (specifiedSize <= 0 || currentDisplaySize <= 0 || specifiedDisplaySize <= 0) + { + return false; + } + + int projectedSize = Math.Max(0, currentDisplaySize + trailingAnchor - leadingAnchor); + + return IsLargerThanExpectedStretchedSize(projectedSize, specifiedSize, currentDisplaySize, specifiedDisplaySize); + } + + private static bool IsSpecifiedDisplaySizeBetweenOriginalAndCurrent( + int specifiedDisplaySize, + int originalDisplaySize, + int currentDisplaySize) + { + if (specifiedDisplaySize <= 0 || originalDisplaySize <= 0 || currentDisplaySize <= 0) + { + return false; + } + + return specifiedDisplaySize > originalDisplaySize + && specifiedDisplaySize < currentDisplaySize; + } + private static int GetEffectiveSpecifiedSizeForStretchRefresh( int anchorDisplaySize, int leadingAnchor,