diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/AnchorLayoutHighDpiRegressionDemoForm.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/AnchorLayoutHighDpiRegressionDemoForm.cs
deleted file mode 100644
index a86ce829d64..00000000000
--- a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/AnchorLayoutHighDpiRegressionDemoForm.cs
+++ /dev/null
@@ -1,304 +0,0 @@
-using System.Drawing;
-#if NET
-using System.Runtime.InteropServices;
-#endif
-using System.Windows.Forms;
-
-namespace Demo;
-
-///
-/// Demo form that reproduces the anchor-layout high-DPI regression (WI00955507).
-/// See anchor-layout-highDpi-regression.md for the full analysis.
-///
-///
-///
-/// The regression manifests when ScaleHelper.IsScalingRequirementMet returns
-/// — i.e. when at least one monitor is at a DPI scale above 100%.
-/// The demo application must run with HighDpiMode.SystemAware (see Program.cs).
-///
-///
-/// Symptom: OtherReferenceTextBox is displaced below — or entirely outside — the
-/// MiscGroupBox client area after the form is shown. The title bar reports the
-/// layout outcome once the form becomes visible.
-///
-///
-/// Fix: add "System.Windows.Forms.AnchorLayoutV2": true to the app's
-/// runtimeconfig.json to enable the V2 deferred-anchor path.
-///
-///
-/// Observed platform behavior (counter-intuitive): this demo fails on
-/// .NET Framework 4.8 even at 100% DPI (96 dpi), while it passes on the WTG
-/// .NET 10 fork. The forced transient MiscGroupBox height of 50 px means
-/// the captured Bottom anchor offset is always a large positive value
-/// (e.g. +230 px at 96 dpi), which displaces OtherReferenceTextBox far below
-/// the group box regardless of DPI scale. .NET Framework has no mechanism to correct
-/// a stale positive anchor. The WTG fork's Solution 3 repair in
-/// DefaultLayout.AnchorLayoutCompat.cs detects the positive offset via
-/// ShouldRefreshAnchorInfoForStalePositiveAnchors and recomputes it against the
-/// stable parent DisplayRectangle on the next layout pass, which is why the
-/// demo passes on .NET 10 even before any DPI scaling is involved.
-///
-///
-public class AnchorLayoutHighDpiRegressionDemoForm : Form
-{
-#if NET
- private static readonly string s_frameworkDescription = RuntimeInformation.FrameworkDescription;
-#endif
- private static readonly int s_scaleDpi = GetScaleDpi();
- private static readonly Size s_miscGroupBoxSize = ScaledSize(324, 240);
- private static readonly int s_expectedBottomMargin = Scale(10);
-
- private readonly GroupBox _miscGroupBox;
- private readonly TextBox _otherReferenceTextBox;
- private readonly CheckBox _goodsServicesIndicatorCheckBox;
- private readonly CheckBox _royaltyPaymentIndicatorCheckBox;
- private readonly TextBox _diagnosticsTextBox;
-
- // Captured in OnLoad (before the form becomes visible) to mirror the
- // "before-show" diagnostics logged by DefaultLayoutTest.
- private Rectangle _miscGroupBoxClientAtAnchorCapture;
- private Rectangle _otherRefBoundsBeforeShow;
- private Rectangle _miscGroupBoxClientBeforeShow;
-
- public AnchorLayoutHighDpiRegressionDemoForm()
- {
- Font = new Font(new FontFamily("Microsoft Sans Serif"), 8.25f);
-
- _diagnosticsTextBox = new TextBox
- {
- Name = "DiagnosticsTextBox",
- Dock = DockStyle.Bottom,
- Multiline = true,
- ReadOnly = true,
- ScrollBars = ScrollBars.Vertical,
- Height = Scale(96),
- TabStop = false
- };
-
- // Mirror the hierarchy and initialization order from the CargoWise repro:
- // TabControl → TabPage → GroupBox, with the GroupBox already parented into the
- // tab hierarchy before its bottom-anchored child is added.
- TabControl invoiceTabControl = new()
- {
- Dock = DockStyle.Fill,
- Name = "InvoiceTabControl",
- MinimumSize = ScaledSize(480, 470),
- Size = ScaledSize(480, 480)
- };
-
- TabPage otherInfoTabPage = new()
- {
- Text = "Other Info",
- Name = "OtherInfoTabPage",
- Location = ScaledPoint(4, 23),
- Padding = new Padding(Scale(3)),
- Size = ScaledSize(480, 323)
- };
-
- // The CargoWise host uses a custom group box with hidden-caption semantics.
- // Using ClientRectangle as DisplayRectangle keeps the reduced repro aligned
- // with the authoritative layout math.
- _miscGroupBox = new CargoWiseLikeGroupBox
- {
- Name = "MiscGroupBox",
- Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left,
- Location = ScaledPoint(7, 3),
- Size = s_miscGroupBoxSize,
- TabStop = false
- };
-
- otherInfoTabPage.Controls.Add(_miscGroupBox);
- invoiceTabControl.Controls.Add(otherInfoTabPage);
-
- Label lastPortDateLabel = new()
- {
- Text = "Date of Direct Shipment:",
- Location = ScaledPoint(16, 75),
- AutoSize = true
- };
-
- TextBox lastPortDateTextBox = new()
- {
- Name = "LastPortDateTextBox",
- Location = ScaledPoint(133, 71),
- Size = ScaledSize(183, 20),
- TabIndex = 2
- };
-
- Label conditionsLabel = new()
- {
- Text = "Conditions:",
- Location = ScaledPoint(16, 101),
- AutoSize = true
- };
-
- TextBox conditionsOfSaleTextBox = new()
- {
- Name = "ConditionsOfSaleTextBox",
- Location = ScaledPoint(133, 97),
- Size = ScaledSize(183, 20),
- TabIndex = 3
- };
-
- Label termsLabel = new()
- {
- Text = "Terms:",
- Location = ScaledPoint(16, 127),
- AutoSize = true
- };
-
- TextBox termsOfPaymentTextBox = new()
- {
- Name = "TermsOfPaymentTextBox",
- Location = ScaledPoint(133, 123),
- Size = ScaledSize(183, 20),
- TabIndex = 4
- };
-
- _goodsServicesIndicatorCheckBox = new CheckBox
- {
- Name = "ServicesIndCheckBox",
- Text = "Goods/Services Indicator",
- Location = ScaledPoint(133, 149),
- AutoSize = true,
- TabIndex = 5,
- UseVisualStyleBackColor = true
- };
-
- _royaltyPaymentIndicatorCheckBox = new CheckBox
- {
- Name = "RoyaltyIndCheckBox",
- Text = "Royalty Payment Indicator",
- Location = ScaledPoint(133, 169),
- AutoSize = true,
- TabIndex = 6,
- UseVisualStyleBackColor = true
- };
-
- _otherReferenceTextBox = new TextBox
- {
- Name = "OtherReferenceTextBox",
- Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
- Location = ScaledPoint(6, 142),
- Multiline = true,
- ScrollBars = ScrollBars.Vertical,
- Size = ScaledSize(310, 88),
- TabIndex = 7
- };
-
- _miscGroupBox.Controls.Add(lastPortDateLabel);
- _miscGroupBox.Controls.Add(lastPortDateTextBox);
- _miscGroupBox.Controls.Add(conditionsLabel);
- _miscGroupBox.Controls.Add(conditionsOfSaleTextBox);
- _miscGroupBox.Controls.Add(termsLabel);
- _miscGroupBox.Controls.Add(termsOfPaymentTextBox);
- _miscGroupBox.Controls.Add(_goodsServicesIndicatorCheckBox);
- _miscGroupBox.Controls.Add(_royaltyPaymentIndicatorCheckBox);
-
- _miscGroupBoxClientAtAnchorCapture = _miscGroupBox.ClientRectangle;
- _miscGroupBox.Controls.Add(_otherReferenceTextBox);
-
- Controls.Add(invoiceTabControl);
- invoiceTabControl.SelectedTab = otherInfoTabPage;
-
- Controls.Add(_diagnosticsTextBox);
-
- ClientSize = ScaledSize(500, 560);
- Name = nameof(AnchorLayoutHighDpiRegressionDemoForm);
- Text = "Anchor Layout High-DPI Regression Demo";
- StartPosition = FormStartPosition.CenterScreen;
- }
-
- ///
- protected override void OnLoad(EventArgs e)
- {
- base.OnLoad(e);
-
- // Capture the pre-show layout state. This mirrors the "before-show" logging in
- // DefaultLayoutTest and is the first place where the anchor-offset bug is visible:
- // if OtherReferenceTextBox.Bounds is already outside MiscGroupBox.ClientRectangle
- // here, the anchor info was captured with a wrong transient parent height.
- _otherRefBoundsBeforeShow = _otherReferenceTextBox.Bounds;
- _miscGroupBoxClientBeforeShow = _miscGroupBox.ClientRectangle;
- }
-
- ///
- protected override void OnShown(EventArgs e)
- {
- base.OnShown(e);
- LogLayoutOutcome();
- }
-
- private void LogLayoutOutcome()
- {
- Rectangle groupBoxClientAfterShow = _miscGroupBox.ClientRectangle;
- Rectangle otherRefBoundsAfterShow = _otherReferenceTextBox.Bounds;
-
- bool withinGroupBox = groupBoxClientAfterShow.Contains(otherRefBoundsAfterShow);
- bool overlapsServices = otherRefBoundsAfterShow.IntersectsWith(_goodsServicesIndicatorCheckBox.Bounds);
- bool overlapsRoyalty = otherRefBoundsAfterShow.IntersectsWith(_royaltyPaymentIndicatorCheckBox.Bounds);
- bool belowIndicators = otherRefBoundsAfterShow.Top >= _royaltyPaymentIndicatorCheckBox.Bounds.Bottom;
- int expectedY = groupBoxClientAfterShow.Height - s_expectedBottomMargin - otherRefBoundsAfterShow.Height;
- bool alignedToExpectedBottomMargin = otherRefBoundsAfterShow.Y == expectedY;
-
- bool pass = withinGroupBox
- && !overlapsServices
- && !overlapsRoyalty
- && belowIndicators
- && alignedToExpectedBottomMargin;
-
- using Graphics g = CreateGraphics();
- int dpi = (int)g.DpiX;
-
- string dpiNote = dpi == 96 ? " (DPI=100%: set a monitor >100% to see FAIL)" : string.Empty;
-
-#if NET
- string runtimeInfo = $"TFM={s_frameworkDescription} | HighDpiMode={Application.HighDpiMode} | ScaleDpi={s_scaleDpi} | DeviceDpi={DeviceDpi} | GraphicsDpi={dpi}";
-#else
- string runtimeInfo = $"TFM=.NET Framework | ScaleDpi={s_scaleDpi} | GraphicsDpi={dpi}";
-#endif
-
- Text = $"{(pass ? "PASS" : "FAIL")}{dpiNote} — DPI={dpi}";
- _diagnosticsTextBox.Text =
- $"ANCHOR-CAPTURE: GroupBox={_miscGroupBoxClientAtAnchorCapture}{Environment.NewLine}" +
- $"BEFORE-SHOW: GroupBox={_miscGroupBoxClientBeforeShow} OtherRef={_otherRefBoundsBeforeShow}{Environment.NewLine}" +
- $"AFTER-SHOW: GroupBox={groupBoxClientAfterShow} OtherRef={otherRefBoundsAfterShow}{Environment.NewLine}" +
- $"EXPECT: BottomMargin={s_expectedBottomMargin} ExpectedY={expectedY} BelowIndicators={belowIndicators}{Environment.NewLine}" +
- runtimeInfo;
- }
-
- private sealed class CargoWiseLikeGroupBox : GroupBox
- {
- public override Rectangle DisplayRectangle => ClientRectangle;
- }
-
- // Scale a logical (96-dpi) pixel value to device pixels, matching the behaviour of
- // ControlDpiScalingHelper.NewScaledPoint/NewScaledSize in the CargoWise codebase.
- private static int GetScaleDpi()
- {
-#if NET
- try
- {
- return (int)GetDpiForSystem();
- }
- catch (EntryPointNotFoundException)
- {
- }
-#endif
-
- using Graphics g = Graphics.FromHwnd(IntPtr.Zero);
-
- return (int)Math.Round(g.DpiX);
- }
-
- private static int Scale(int value) => (int)Math.Round(value * s_scaleDpi / 96.0);
-
-#if NET
- [DllImport("user32.dll")]
- private static extern uint GetDpiForSystem();
-#endif
-
- private static Point ScaledPoint(int x, int y) => new(Scale(x), Scale(y));
-
- private static Size ScaledSize(int w, int h) => new(Scale(w), Scale(h));
-}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MainForm.Designer.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MainForm.Designer.cs
index 03a0ec0fcbd..df5d53c3d5e 100644
--- a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MainForm.Designer.cs
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MainForm.Designer.cs
@@ -1,4 +1,4 @@
-using System.ComponentModel;
+using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
@@ -19,8 +19,6 @@ partial class MainForm
private Label _dataGridDescriptionLabel = null!;
private Button _statusBarButton = null!;
private Label _statusBarDescriptionLabel = null!;
- private Button _anchorLayoutHighDpiRegressionButton = null!;
- private Label _anchorLayoutHighDpiRegressionDescriptionLabel = null!;
protected override void Dispose(bool disposing)
{
@@ -45,8 +43,6 @@ private void InitializeComponent()
_dataGridDescriptionLabel = new Label();
_statusBarButton = new Button();
_statusBarDescriptionLabel = new Label();
- _anchorLayoutHighDpiRegressionButton = new Button();
- _anchorLayoutHighDpiRegressionDescriptionLabel = new Label();
SuspendLayout();
//
// _titleLabel
@@ -140,31 +136,11 @@ private void InitializeComponent()
_statusBarDescriptionLabel.Text = "Simple text mode, panel layout, owner-draw rendering, border styles, and sizing grip behavior.";
_statusBarDescriptionLabel.TextAlign = ContentAlignment.TopCenter;
//
- // _anchorLayoutHighDpiRegressionButton
- //
- _anchorLayoutHighDpiRegressionButton.Location = new Point(210, 378);
- _anchorLayoutHighDpiRegressionButton.Name = "_anchorLayoutHighDpiRegressionButton";
- _anchorLayoutHighDpiRegressionButton.Size = new Size(300, 60);
- _anchorLayoutHighDpiRegressionButton.TabIndex = 10;
- _anchorLayoutHighDpiRegressionButton.Text = "Anchor Layout";
- _anchorLayoutHighDpiRegressionButton.Click += AnchorLayoutHighDpiRegressionButton_Click;
- //
- // _anchorLayoutHighDpiRegressionDescriptionLabel
- //
- _anchorLayoutHighDpiRegressionDescriptionLabel.Location = new Point(210, 442);
- _anchorLayoutHighDpiRegressionDescriptionLabel.Name = "_anchorLayoutHighDpiRegressionDescriptionLabel";
- _anchorLayoutHighDpiRegressionDescriptionLabel.Size = new Size(300, 40);
- _anchorLayoutHighDpiRegressionDescriptionLabel.TabIndex = 11;
- _anchorLayoutHighDpiRegressionDescriptionLabel.Text = "Launches the dedicated high-DPI anchor-layout regression repro in a separate process.";
- _anchorLayoutHighDpiRegressionDescriptionLabel.TextAlign = ContentAlignment.TopCenter;
- //
// MainForm
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(720, 500);
- Controls.Add(_anchorLayoutHighDpiRegressionDescriptionLabel);
- Controls.Add(_anchorLayoutHighDpiRegressionButton);
Controls.Add(_statusBarDescriptionLabel);
Controls.Add(_statusBarButton);
Controls.Add(_toolBarLabel);
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MainForm.cs b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MainForm.cs
index 83dd13d17d0..103d772c650 100644
--- a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MainForm.cs
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Demo/MainForm.cs
@@ -1,4 +1,4 @@
-using System.Windows.Forms;
+using System.Windows.Forms;
namespace Demo;
@@ -32,10 +32,4 @@ private void StatusBarButton_Click(object? sender, EventArgs e)
StatusBarForm form = new();
form.Show(this);
}
-
- private void AnchorLayoutHighDpiRegressionButton_Click(object? sender, EventArgs e)
- {
- AnchorLayoutHighDpiRegressionDemoForm form = new();
- form.Show(this);
- }
}
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
deleted file mode 100644
index b2d9ce0c799..00000000000
--- a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Legacy.Tests/Layout/AnchorLayoutHighDpiRegressionTests.cs
+++ /dev/null
@@ -1,739 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Drawing;
-using System.Reflection;
-using System.Windows.Forms.Layout;
-using System.Windows.Forms.Primitives;
-
-
-namespace System.Windows.Forms.Legacy.Tests;
-
-///
-/// Issue-specific regression tests for WI00955507.
-///
-///
-///
-/// These tests stay focused on the real CargoWise regression shape: a bottom/right-anchored
-/// text box inside a group box inside a tab page, under high DPI, with transient parent
-/// geometry during anchor capture.
-///
-///
-/// See anchor-layout-highDpi-regression.md for the full analysis.
-///
-///
-public class AnchorLayoutHighDpiRegressionTests
-{
- private const string AnchorLayoutV2SwitchName = "System.Windows.Forms.AnchorLayoutV2";
- private static readonly int s_scaleDpi = GetScaleDpi();
-
- private readonly ITestOutputHelper _output;
-
- public AnchorLayoutHighDpiRegressionTests(ITestOutputHelper output)
- {
- _output = output;
- }
-
- ///
- /// Sets the AnchorLayoutV2 AppContext switch and resets the
- /// cached field so the new value is
- /// actually observed — without relying on TestSwitch.LocalAppContext.DisableCaching
- /// in a runtimeconfig file.
- ///
- private static void SetAnchorLayoutV2Switch(bool value)
- {
- // LocalAppContextSwitches caches switch values in a private static int field
- // (0 = uncached, 1 = true, -1 = false). Reset it to 0 so the next access
- // re-reads the value from AppContext rather than short-circuiting from cache.
- typeof(LocalAppContextSwitches)
- .GetField("s_anchorLayoutV2", BindingFlags.NonPublic | BindingFlags.Static)!
- .SetValue(null, 0);
-
- AppContext.SetSwitch(AnchorLayoutV2SwitchName, value);
- }
-
- private static int ScaleLogicalPixels(int value, float dpi) => (int)Math.Round(value * dpi / 96f);
-
- private static int ScaleLogicalPixels(int value) => (int)Math.Round(value * s_scaleDpi / 96f);
-
- private static Point ScaledPoint(int x, int y) => new(ScaleLogicalPixels(x), ScaleLogicalPixels(y));
-
- private static Size ScaledSize(int width, int height) => new(ScaleLogicalPixels(width), ScaleLogicalPixels(height));
-
- private static int GetScaleDpi()
- {
- using Graphics graphics = Graphics.FromHwnd(IntPtr.Zero);
-
- return (int)Math.Round(graphics.DpiX);
- }
-
- ///
- /// Integration-level regression: replicates the OtherReferenceTextBox scenario from
- /// DefaultLayoutTest.cs (WI00955507) using standard WinForms controls.
- /// A bottom/right-anchored inside a inside a
- /// must remain fully contained within the group box after the form
- /// is shown and layout is complete.
- ///
- [StaFact]
- public void BottomRightAnchoredTextBoxInsideGroupBoxInsideTabPage_WithAnchorLayoutV2_RemainsWithinGroupBoxAfterFormIsShown()
- {
- SetAnchorLayoutV2Switch(true);
- try
- {
- // Sizes chosen so that the textbox's design-time Bottom does NOT exceed the
- // group box's final client height, which avoids a permanent deferral.
- // This mirrors the net48 geometry from the CargoWise investigation.
- using Form form = new() { ClientSize = new Size(400, 430) };
- using TabControl tabControl = new() { Dock = DockStyle.Fill };
- using TabPage tabPage = new();
- using GroupBox groupBox = new()
- {
- Location = new Point(7, 3),
- Size = new Size(324, 240),
- Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left
- };
- using TextBox otherRef = new()
- {
- // Anchor = Bottom | Left | Right — must float to the bottom of the group box.
- Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
- Location = new Point(6, 142),
- Size = new Size(310, 88),
- Multiline = true
- };
-
- groupBox.Controls.Add(otherRef);
- tabPage.Controls.Add(groupBox);
- tabControl.TabPages.Add(tabPage);
- tabControl.SelectedTab = tabPage;
- form.Controls.Add(tabControl);
-
- form.Show();
- Application.DoEvents();
- form.PerformLayout();
- Application.DoEvents();
- form.Close();
-
- Assert.True(
- groupBox.ClientRectangle.Contains(otherRef.Bounds),
- $"OtherRef should be fully inside GroupBox. "
- + $"GroupBox.ClientRectangle={groupBox.ClientRectangle}, OtherRef.Bounds={otherRef.Bounds}");
-
- Assert.True(otherRef.Visible, "OtherRef should be visible after form is shown.");
- }
- finally
- {
- SetAnchorLayoutV2Switch(false);
- }
- }
-
- ///
- /// Mirrors the authoritative CargoWise initialization order under the V1 path using
- /// standard WinForms controls plus a GroupBox whose
- /// matches the hidden-caption semantics of the production host.
- ///
- [StaFact]
- public void BottomRightAnchoredTextBoxInsideGroupBoxInsideTabPage_V1Path_CargoWiseGeometry_ShouldRemainWithinGroupBoxAfterFormIsShown()
- {
- bool setHighDpiModeResult = Application.SetHighDpiMode(HighDpiMode.SystemAware);
- _output.WriteLine($"{nameof(AnchorLayoutHighDpiRegressionTests)}: SetHighDpiMode(SystemAware)={setHighDpiModeResult}, HighDpiMode={Application.HighDpiMode}");
-
- using Form form = new() { ClientSize = ScaledSize(500, 560) };
- using TabControl tabControl = new()
- {
- Dock = DockStyle.Fill,
- MinimumSize = ScaledSize(480, 470),
- Size = ScaledSize(480, 480)
- };
- using TabPage tabPage = new()
- {
- Location = ScaledPoint(4, 23),
- Padding = new Padding(ScaleLogicalPixels(3)),
- Size = ScaledSize(480, 323)
- };
- using CargoWiseLikeGroupBox groupBox = new();
- using TextBox lastPortDate = new()
- {
- Location = ScaledPoint(133, 71),
- Size = ScaledSize(183, 20),
- TabIndex = 2
- };
- using TextBox conditionsOfSale = new()
- {
- Location = ScaledPoint(133, 97),
- Size = ScaledSize(183, 20),
- TabIndex = 3
- };
- using TextBox termsOfPayment = new()
- {
- Location = ScaledPoint(133, 123),
- Size = ScaledSize(183, 20),
- TabIndex = 4
- };
- using CheckBox servicesIndicator = new()
- {
- Text = "Goods/Services Indicator",
- Location = ScaledPoint(133, 149),
- AutoSize = true
- };
- using CheckBox royaltyIndicator = new()
- {
- Text = "Royalty Payment Indicator",
- Location = ScaledPoint(133, 169),
- AutoSize = true
- };
- using TextBox otherRef = new()
- {
- Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
- Location = ScaledPoint(6, 142),
- Size = ScaledSize(310, 88),
- Multiline = true,
- ScrollBars = ScrollBars.Vertical,
- TabIndex = 7
- };
-
- groupBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left;
- groupBox.Location = ScaledPoint(7, 3);
- groupBox.Size = ScaledSize(324, 240);
- groupBox.TabStop = false;
-
- tabPage.Controls.Add(groupBox);
- tabControl.TabPages.Add(tabPage);
-
- groupBox.Controls.Add(lastPortDate);
- groupBox.Controls.Add(conditionsOfSale);
- groupBox.Controls.Add(termsOfPayment);
- groupBox.Controls.Add(servicesIndicator);
- groupBox.Controls.Add(royaltyIndicator);
- groupBox.Controls.Add(otherRef);
-
- tabControl.SelectedTab = tabPage;
- form.Controls.Add(tabControl);
-
- form.Show();
- Application.DoEvents();
- form.PerformLayout();
- Application.DoEvents();
-
- using Graphics graphics = form.CreateGraphics();
- _output.WriteLine($"{nameof(AnchorLayoutHighDpiRegressionTests)}: GraphicsDpiX={graphics.DpiX}, GraphicsDpiY={graphics.DpiY}");
-
- int expectedY = groupBox.ClientRectangle.Height - ScaleLogicalPixels(10, graphics.DpiY) - otherRef.Bounds.Height;
- _output.WriteLine($"Post-show: GroupBox.ClientRectangle={groupBox.ClientRectangle}, GroupBox.DisplayRectangle={groupBox.DisplayRectangle}, OtherRef.Bounds={otherRef.Bounds}, ExpectedY={expectedY}, ServicesIndicator={servicesIndicator.Bounds}, RoyaltyIndicator={royaltyIndicator.Bounds}");
-
- Assert.True(
- groupBox.ClientRectangle.Contains(otherRef.Bounds),
- $"CargoWise geometry: OtherRef should be fully inside GroupBox. "
- + $"GroupBox.ClientRectangle={groupBox.ClientRectangle}, OtherRef.Bounds={otherRef.Bounds}");
-
- Assert.True(
- groupBox.DisplayRectangle.Contains(otherRef.Bounds),
- $"CargoWise geometry: OtherRef should be fully inside GroupBox display area. "
- + $"GroupBox.DisplayRectangle={groupBox.DisplayRectangle}, OtherRef.Bounds={otherRef.Bounds}");
-
- Assert.Equal(expectedY, otherRef.Location.Y);
-
- Assert.False(
- otherRef.Bounds.IntersectsWith(servicesIndicator.Bounds),
- $"CargoWise geometry: OtherRef should not overlap ServicesIndicator. "
- + $"ServicesIndicator={servicesIndicator.Bounds}, OtherRef={otherRef.Bounds}");
-
- Assert.False(
- otherRef.Bounds.IntersectsWith(royaltyIndicator.Bounds),
- $"CargoWise geometry: OtherRef should not overlap RoyaltyIndicator. "
- + $"RoyaltyIndicator={royaltyIndicator.Bounds}, OtherRef={otherRef.Bounds}");
-
- Assert.True(
- otherRef.Bounds.Top >= royaltyIndicator.Bounds.Bottom,
- $"CargoWise geometry: OtherRef should be below RoyaltyIndicator. "
- + $"RoyaltyIndicator={royaltyIndicator.Bounds}, OtherRef={otherRef.Bounds}");
-
- Assert.Equal(ScaleLogicalPixels(6, graphics.DpiX), otherRef.Location.X);
-
- form.Close();
- }
-
- [StaFact]
- public void ShouldRefreshAnchorInfoForStalePositiveAnchors_BottomAnchoredTrailingAxis_ReturnsTrue()
- {
- DefaultLayout.AnchorInfo anchorInfo = CreateAnchorInfo(
- left: 10,
- top: 70,
- right: -190,
- bottom: 170,
- displayRectangle: new Rectangle(0, 0, 300, 100));
- Rectangle bounds = new(10, 120, 100, 50);
- Rectangle currentDisplayRectangle = new(0, 0, 300, 200);
-
- bool shouldRefresh = ShouldRefreshAnchorInfoForStalePositiveAnchors(
- anchorInfo,
- bounds,
- currentDisplayRectangle,
- AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right);
-
- Assert.True(
- shouldRefresh,
- $"A trailing-only bottom anchor with a positive cached bottom offset should be refreshed. "
- + $"Bounds={bounds}, DisplayRectangle={currentDisplayRectangle}, CachedBottom={anchorInfo.Bottom}");
- }
-
- [StaFact]
- public void ShouldRefreshAnchorInfoForStalePositiveAnchors_StretchAnchoredVerticalAxis_ReturnsFalse()
- {
- DefaultLayout.AnchorInfo anchorInfo = CreateAnchorInfo(
- left: 8,
- top: 0,
- right: 656,
- bottom: 6,
- displayRectangle: new Rectangle(6, 6, 788, 474));
- Rectangle bounds = new(14, 6, 648, 766);
- Rectangle currentDisplayRectangle = new(6, 6, 772, 760);
-
- bool shouldRefresh = ShouldRefreshAnchorInfoForStalePositiveAnchors(
- anchorInfo,
- bounds,
- currentDisplayRectangle,
- AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left);
-
- Assert.False(
- shouldRefresh,
- $"A stretch-anchored vertical axis should not be treated as a stale positive bottom anchor. "
- + $"Bounds={bounds}, DisplayRectangle={currentDisplayRectangle}, CachedBottom={anchorInfo.Bottom}");
- }
-
- [StaFact]
- public void ShouldRefreshAnchorInfoForStalePositiveAnchors_RightAnchoredTrailingAxis_ReturnsTrue()
- {
- // Symmetric horizontal-axis case: Right without Left, stale positive right offset.
- // Captured right = 140 against a parent width of 100 (transient), parent has since grown to 300.
- DefaultLayout.AnchorInfo anchorInfo = CreateAnchorInfo(
- left: 190,
- top: 10,
- right: 140,
- bottom: -40,
- displayRectangle: new Rectangle(0, 0, 100, 200));
- Rectangle bounds = new(190, 10, 90, 40);
- Rectangle currentDisplayRectangle = new(0, 0, 300, 200);
-
- bool shouldRefresh = ShouldRefreshAnchorInfoForStalePositiveAnchors(
- anchorInfo,
- bounds,
- currentDisplayRectangle,
- AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom);
-
- Assert.True(
- shouldRefresh,
- $"A trailing-only right anchor with a positive cached right offset should be refreshed. "
- + $"Bounds={bounds}, DisplayRectangle={currentDisplayRectangle}, CachedRight={anchorInfo.Right}");
- }
-
- ///
- /// Fast local regression for the current CargoWise-only issue: a stretch-anchored parent is
- /// recovered once from its original captured display rectangle, then is incorrectly eligible
- /// for a second stale-positive refresh that overwrites the recovered anchors.
- ///
- [StaFact]
- public void StretchAnchoredGroupBox_RecoveredStretchAnchor_ShouldNotTriggerFollowUpPositiveRefresh()
- {
- using StretchTestContainer parent = new()
- {
- Bounds = new Rectangle(8, 40, 784, 772),
- SimulatedDisplayRectangle = new Rectangle(6, 6, 772, 760)
- };
- using CargoWiseLikeGroupBox groupBox = new()
- {
- Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left,
- Bounds = new Rectangle(14, 6, 648, 100)
- };
-
- parent.Controls.Add(groupBox);
-
- SetSpecifiedBounds(parent, new Rectangle(8, 40, 784, 772));
- SetSpecifiedBounds(groupBox, new Rectangle(14, 6, 648, 480));
-
- DefaultLayout.AnchorInfo anchorInfo = CreateAnchorInfo(8, 0, 656, -374, new Rectangle(6, 6, 788, 474));
- Rectangle currentDisplayRectangle = parent.DisplayRectangle;
-
- RefreshAnchorInfoForDisplayRectangleGrowth(groupBox, anchorInfo, currentDisplayRectangle, groupBox.Anchor, isStretchAnchorRefresh: true);
-
- Rectangle recoveredBounds = ComputeAnchoredBounds(anchorInfo, currentDisplayRectangle, groupBox.Anchor);
- groupBox.Bounds = recoveredBounds;
- SetSpecifiedBounds(groupBox, new Rectangle(14, 6, 648, 480));
-
- bool shouldRefreshAgain = ShouldRefreshAnchorInfoForStalePositiveAnchors(anchorInfo, recoveredBounds, currentDisplayRectangle, groupBox.Anchor);
-
- _output.WriteLine($"Recovered stretch anchor bounds={recoveredBounds}, shouldRefreshAgain={shouldRefreshAgain}, recoveredBottom={anchorInfo.Bottom}, recoveredDisplayRect={anchorInfo.DisplayRectangle}");
-
- Assert.False(
- shouldRefreshAgain,
- $"Recovered stretch anchor should not trigger a follow-up stale-positive refresh. "
- + $"RecoveredBounds={recoveredBounds}, RecoveredBottom={anchorInfo.Bottom}, RecoveredDisplayRect={anchorInfo.DisplayRectangle}");
- }
-
- [StaFact]
- public void StretchAnchoredControl_WithOversizedPositiveOffsets_RefreshesUsingSpecifiedDisplayRectangle()
- {
- using StretchTestContainer parent = new()
- {
- Bounds = new Rectangle(0, 0, 1458, 712),
- SimulatedDisplayRectangle = new Rectangle(0, 0, 1458, 712)
- };
- using CargoWiseLikeGroupBox stretchControl = new()
- {
- Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
- Bounds = new Rectangle(3, 160, 1530, 840)
- };
-
- parent.Controls.Add(stretchControl);
-
- // Designer-time (specified) geometry captured by the layout engine.
- SetSpecifiedBounds(parent, new Rectangle(0, 0, 651, 474));
- SetSpecifiedBounds(stretchControl, new Rectangle(3, 160, 645, 311));
-
- // Stale V1 anchor info: positive trailing offsets make the control larger than parent.
- DefaultLayout.AnchorInfo anchorInfo = CreateAnchorInfo(
- left: 3,
- top: 160,
- right: 75,
- bottom: 50,
- displayRectangle: new Rectangle(0, 0, 576, 400));
-
- Rectangle currentDisplayRectangle = parent.DisplayRectangle;
-
- RefreshAnchorInfoForDisplayRectangleGrowth(
- stretchControl,
- anchorInfo,
- currentDisplayRectangle,
- stretchControl.Anchor,
- isStretchAnchorRefresh: true,
- useSpecifiedDisplayRectangleForStretchRefresh: true);
-
- Rectangle refreshedBounds = ComputeAnchoredBounds(anchorInfo, currentDisplayRectangle, stretchControl.Anchor);
-
- Assert.True(anchorInfo.Right < 0, $"Expected negative right anchor after refresh but got {anchorInfo.Right}.");
- Assert.True(anchorInfo.Bottom < 0, $"Expected negative bottom anchor after refresh but got {anchorInfo.Bottom}.");
- Assert.True(
- parent.DisplayRectangle.Contains(refreshedBounds),
- $"Refreshed stretch bounds should fit in parent display area. "
- + $"ParentDisplayRect={parent.DisplayRectangle}, RefreshedBounds={refreshedBounds}");
- }
-
- [StaFact]
- public void StretchAnchoredControl_WithSpecifiedBoundsMuchLargerThanBaseline_UsesCachedBoundsDuringStretchRefresh()
- {
- using StretchTestContainer parent = new()
- {
- Bounds = new Rectangle(0, 0, 1526, 453),
- SimulatedDisplayRectangle = new Rectangle(0, 0, 1526, 453)
- };
- using CargoWiseLikeGroupBox stretchControl = new()
- {
- Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
- Bounds = new Rectangle(7, 400, 1477, 0)
- };
-
- parent.Controls.Add(stretchControl);
-
- // CargoWise trace shape: runtime bounds fit the baseline while specified bounds are much larger.
- SetSpecifiedBounds(parent, new Rectangle(0, 0, 1492, 405));
- SetSpecifiedBounds(stretchControl, new Rectangle(7, 400, 1612, 777));
-
- DefaultLayout.AnchorInfo anchorInfo = CreateAnchorInfo(
- left: 7,
- top: 400,
- right: -8,
- bottom: -5,
- displayRectangle: new Rectangle(0, 0, 1492, 405));
-
- RefreshAnchorInfoForDisplayRectangleGrowth(
- stretchControl,
- anchorInfo,
- parent.DisplayRectangle,
- stretchControl.Anchor,
- isStretchAnchorRefresh: true,
- useSpecifiedDisplayRectangleForStretchRefresh: false);
-
- Rectangle refreshedBounds = ComputeAnchoredBounds(anchorInfo, parent.DisplayRectangle, stretchControl.Anchor);
-
- Assert.True(
- anchorInfo.Right < 0,
- $"Expected negative right anchor after refresh but got {anchorInfo.Right}.");
- Assert.True(
- anchorInfo.Bottom < 0,
- $"Expected negative bottom anchor after refresh but got {anchorInfo.Bottom}.");
- Assert.True(
- parent.DisplayRectangle.Contains(refreshedBounds),
- $"Refreshed stretch bounds should fit in parent display area. "
- + $"ParentDisplayRect={parent.DisplayRectangle}, RefreshedBounds={refreshedBounds}");
- }
-
- [StaFact]
- public void StretchAnchoredControl_WithStaleSpecifiedDisplayRectangle_DoesNotRefreshStretchAnchors()
- {
- using StretchTestContainer parent = new()
- {
- Bounds = new Rectangle(0, 0, 1492, 405),
- SimulatedDisplayRectangle = new Rectangle(0, 0, 1492, 405)
- };
- using CargoWiseLikeGroupBox stretchControl = new()
- {
- Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
- Bounds = new Rectangle(7, 400, 1477, 0)
- };
-
- parent.Controls.Add(stretchControl);
-
- // Stale specified metadata still points at the pre-shrink baseline.
- SetSpecifiedBounds(parent, new Rectangle(0, 0, 1627, 1185));
- SetSpecifiedBounds(stretchControl, new Rectangle(7, 400, 1612, 777));
-
- DefaultLayout.SetAnchorInfo(
- stretchControl,
- CreateAnchorInfo(
- left: 7,
- top: 400,
- right: -8,
- bottom: -5,
- displayRectangle: new Rectangle(0, 0, 1492, 405)));
-
- parent.Bounds = new Rectangle(0, 0, 1526, 453);
- parent.SimulatedDisplayRectangle = new Rectangle(0, 0, 1526, 453);
-
- parent.PerformLayout();
-
- DefaultLayout.AnchorInfo refreshedAnchorInfo = DefaultLayout.GetAnchorInfo(stretchControl)!;
-
- Assert.Equal(-8, refreshedAnchorInfo.Right);
- Assert.Equal(-5, refreshedAnchorInfo.Bottom);
- Assert.Equal(new Rectangle(7, 400, 1511, 48), stretchControl.Bounds);
- }
-
- [StaFact]
- public void StretchAnchoredControl_WithHealthyNegativeTrailingAnchor_StretchesWithoutRefreshingAnchors()
- {
- using StretchTestContainer parent = new()
- {
- Bounds = new Rectangle(0, 0, 1492, 405),
- SimulatedDisplayRectangle = new Rectangle(0, 0, 1492, 405)
- };
- using Panel stretchControl = new()
- {
- Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
- Bounds = new Rectangle(7, 175, 1477, 217)
- };
-
- parent.Controls.Add(stretchControl);
-
- // Baseline metadata is healthy; a growth pass should stretch using existing anchors.
- SetSpecifiedBounds(parent, new Rectangle(0, 0, 1492, 405));
- SetSpecifiedBounds(stretchControl, new Rectangle(7, 175, 1477, 217));
-
- DefaultLayout.SetAnchorInfo(
- stretchControl,
- CreateAnchorInfo(
- left: 7,
- top: 175,
- right: -8,
- bottom: 392,
- displayRectangle: new Rectangle(0, 0, 1492, 405)));
-
- parent.Bounds = new Rectangle(0, 0, 1526, 453);
- parent.SimulatedDisplayRectangle = new Rectangle(0, 0, 1526, 453);
-
- parent.PerformLayout();
-
- DefaultLayout.AnchorInfo refreshedAnchorInfo = DefaultLayout.GetAnchorInfo(stretchControl)!;
-
- Assert.Equal(-8, refreshedAnchorInfo.Right);
- Assert.Equal(392, refreshedAnchorInfo.Bottom);
- Assert.Equal(new Rectangle(7, 175, 1511, 217), stretchControl.Bounds);
- }
-
- [StaFact]
- public void StretchAnchoredControl_WithStaleSpecifiedSizeAndHealthyAnchors_DoesNotRefreshAnchors()
- {
- using StretchTestContainer parent = new()
- {
- Bounds = new Rectangle(0, 0, 1492, 405),
- SimulatedDisplayRectangle = new Rectangle(0, 0, 1492, 405)
- };
- using Panel stretchControl = new()
- {
- Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right,
- Bounds = new Rectangle(7, 175, 1477, 217)
- };
-
- parent.Controls.Add(stretchControl);
-
- // Parent specified metadata is current, but child specified size is stale and oversized.
- SetSpecifiedBounds(parent, new Rectangle(0, 0, 1492, 405));
- SetSpecifiedBounds(stretchControl, new Rectangle(7, 175, 1612, 217));
-
- DefaultLayout.SetAnchorInfo(
- stretchControl,
- CreateAnchorInfo(
- left: 7,
- top: 175,
- right: -8,
- bottom: 392,
- displayRectangle: new Rectangle(0, 0, 1492, 405)));
-
- parent.Bounds = new Rectangle(0, 0, 1526, 453);
- parent.SimulatedDisplayRectangle = new Rectangle(0, 0, 1526, 453);
-
- parent.PerformLayout();
-
- DefaultLayout.AnchorInfo refreshedAnchorInfo = DefaultLayout.GetAnchorInfo(stretchControl)!;
-
- Assert.Equal(-8, refreshedAnchorInfo.Right);
- Assert.Equal(392, refreshedAnchorInfo.Bottom);
- Assert.Equal(new Rectangle(7, 175, 1511, 217), stretchControl.Bounds);
- }
-
- [StaFact]
- public void StretchAnchoredControl_WithOversizedPositiveOffsetsAndNoDisplayGrowth_RefreshesUsingSpecifiedDisplayRectangle()
- {
- using StretchTestContainer parent = new()
- {
- Bounds = new Rectangle(0, 0, 576, 400),
- SimulatedDisplayRectangle = new Rectangle(0, 0, 576, 400)
- };
- using CargoWiseLikeGroupBox stretchControl = new()
- {
- Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
- Bounds = new Rectangle(3, 160, 645, 311)
- };
-
- parent.Controls.Add(stretchControl);
-
- // Designer-time (specified) geometry from the CargoWise layout shape.
- SetSpecifiedBounds(parent, new Rectangle(0, 0, 651, 474));
- SetSpecifiedBounds(stretchControl, new Rectangle(3, 160, 645, 311));
-
- // Stale V1 anchors captured against the current display rectangle already contain
- // positive trailing offsets, so there is no display-rectangle growth signal.
- DefaultLayout.AnchorInfo anchorInfo = CreateAnchorInfo(
- left: 3,
- top: 160,
- right: 72,
- bottom: 71,
- displayRectangle: parent.DisplayRectangle);
-
- Rectangle currentDisplayRectangle = parent.DisplayRectangle;
-
- RefreshAnchorInfoForDisplayRectangleGrowth(
- stretchControl,
- anchorInfo,
- currentDisplayRectangle,
- stretchControl.Anchor,
- isStretchAnchorRefresh: true,
- useSpecifiedDisplayRectangleForStretchRefresh: true);
-
- Rectangle refreshedBounds = ComputeAnchoredBounds(anchorInfo, currentDisplayRectangle, stretchControl.Anchor);
-
- Assert.True(
- anchorInfo.Right < 0,
- $"Expected negative right anchor after refresh but got {anchorInfo.Right}.");
- Assert.True(
- anchorInfo.Bottom < 0,
- $"Expected negative bottom anchor after refresh but got {anchorInfo.Bottom}.");
- Assert.True(
- parent.DisplayRectangle.Contains(refreshedBounds),
- $"Refreshed stretch bounds should fit in parent display area. "
- + $"ParentDisplayRect={parent.DisplayRectangle}, RefreshedBounds={refreshedBounds}");
- }
-
- private static DefaultLayout.AnchorInfo CreateAnchorInfo(int left, int top, int right, int bottom, Rectangle displayRectangle) =>
- new()
- {
- Left = left,
- Top = top,
- Right = right,
- Bottom = bottom,
- DisplayRectangle = displayRectangle
- };
-
- private static void SetSpecifiedBounds(Control control, Rectangle bounds) =>
- CommonProperties.UpdateSpecifiedBounds(control, bounds.X, bounds.Y, bounds.Width, bounds.Height);
-
- private static bool ShouldRefreshAnchorInfoForStalePositiveAnchors(DefaultLayout.AnchorInfo anchorInfo, Rectangle bounds, Rectangle displayRectangle, AnchorStyles anchor) =>
- DefaultLayout.ShouldRefreshAnchorInfoForStalePositiveAnchors(anchorInfo, bounds, displayRectangle, anchor);
-
- private static void RefreshAnchorInfoForDisplayRectangleGrowth(
- Control control,
- DefaultLayout.AnchorInfo anchorInfo,
- Rectangle displayRectangle,
- AnchorStyles anchor,
- bool isStretchAnchorRefresh,
- bool useSpecifiedDisplayRectangleForStretchRefresh = false) =>
- DefaultLayout.RefreshAnchorInfoForDisplayRectangleGrowth(
- control,
- anchorInfo,
- displayRectangle,
- anchor,
- isStretchAnchorRefresh,
- useSpecifiedDisplayRectangleForStretchRefresh);
-
- private static Rectangle ComputeAnchoredBounds(DefaultLayout.AnchorInfo anchorInfo, Rectangle displayRectangle, AnchorStyles anchor)
- {
- int left = anchorInfo.Left + displayRectangle.X;
- int top = anchorInfo.Top + displayRectangle.Y;
- int right = anchorInfo.Right + displayRectangle.X;
- int bottom = anchorInfo.Bottom + displayRectangle.Y;
-
- if ((anchor & AnchorStyles.Right) != AnchorStyles.None)
- {
- right += displayRectangle.Width;
-
- if ((anchor & AnchorStyles.Left) == AnchorStyles.None)
- {
- left += displayRectangle.Width;
- }
- }
- else if ((anchor & AnchorStyles.Left) == AnchorStyles.None)
- {
- int center = displayRectangle.Width / 2;
- right += center;
- left += center;
- }
-
- if ((anchor & AnchorStyles.Bottom) != AnchorStyles.None)
- {
- bottom += displayRectangle.Height;
-
- if ((anchor & AnchorStyles.Top) == AnchorStyles.None)
- {
- top += displayRectangle.Height;
- }
- }
- else if ((anchor & AnchorStyles.Top) == AnchorStyles.None)
- {
- int center = displayRectangle.Height / 2;
- bottom += center;
- top += center;
- }
-
- if (right < left)
- {
- right = left;
- }
-
- if (bottom < top)
- {
- bottom = top;
- }
-
- return new Rectangle(left, top, right - left, bottom - top);
- }
-
- private sealed class CargoWiseLikeGroupBox : GroupBox
- {
- public override Rectangle DisplayRectangle => ClientRectangle;
- }
-
- private sealed class StretchTestContainer : Panel
- {
- public Rectangle SimulatedDisplayRectangle { get; set; }
-
- public override Rectangle DisplayRectangle => SimulatedDisplayRectangle;
- }
-}
diff --git a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Package/System.Windows.Forms.Package.csproj b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Package/System.Windows.Forms.Package.csproj
index 359cb207ff6..81e02fec083 100644
--- a/src/System.Windows.Forms.Legacy/System.Windows.Forms.Package/System.Windows.Forms.Package.csproj
+++ b/src/System.Windows.Forms.Legacy/System.Windows.Forms.Package/System.Windows.Forms.Package.csproj
@@ -10,7 +10,7 @@
0.1.0
$(WtgPackageVersionPrefix)-dev
WiseTech Global forked version of System.Windows.Forms that includes controls that were deprecated in .NET Core 3.1 and .NET 5, like DataGrid, Menu, ToolBar and StatusBar.
- Rollback custom binding context support in BindingContext/RelatedCurrencyManager, PR link: https://github.com/WiseTechGlobal/winforms/pull/20
+ Remove IsScalingRequirementMet from DefaultLayout.cs, remove the DefaultLayout.AnchorLayoutCompat.cs file, PR link: https://github.com/WiseTechGlobal/winforms/pull/38
WiseTech Global
© WiseTech Global Limited. All rights reserved.
README.md
diff --git a/src/System.Windows.Forms.Legacy/anchor-layout-highDpi-regression.md b/src/System.Windows.Forms.Legacy/anchor-layout-highDpi-regression.md
deleted file mode 100644
index e8c2a57409e..00000000000
--- a/src/System.Windows.Forms.Legacy/anchor-layout-highDpi-regression.md
+++ /dev/null
@@ -1,358 +0,0 @@
-# Anchor Layout High-DPI Regression: .NET Framework vs .NET 8+
-
-Controls anchored to `Bottom` or `Right` can appear displaced or invisible in .NET 8+ on any
-monitor scaled above 100% DPI, while the same code is correct under .NET Framework 4.8.
-
-The bug lives entirely in the **V1 anchor path** (`UpdateAnchorInfo` plus the compat repair in
-`ComputeAnchoredBounds`) and does **not** affect `AnchorLayoutV2`.
-
-Two separate V1 compat failures exist in the .NET 10 path:
-
-- A **stale positive anchor capture** for controls anchored only to trailing edges (`Bottom`
- without `Top`, or `Right` without `Left`).
-- A **stretch-anchor recovery double-refresh** where a stretch-anchored parent is recovered from
- the wrong baseline and then immediately re-qualifies for the stale-positive repair on the same
- axis, collapsing the recovered height.
-
-`AnchorLayoutV2` eliminates the root cause (see [Solution 1](#solution-1--enable-anchorlayoutv2-recommended)).
-The **fork compat repair** resolves both failures at the framework level without requiring an application
-change (see [Solution 3](#solution-3--additive-layout-side-repair-current-fork-implementation)).
-
----
-
-## Problem Description
-
-### Symptoms
-
-On any machine where at least one monitor is scaled above 100% DPI (e.g. 200%), controls with
-`Anchor` involving `Bottom` or `Right` inside deeply nested containers (e.g. `GroupBox` →
-`TabPage` → `TabControl`) are positioned far outside their parent's client area when the form
-becomes visible — or are completely invisible.
-
-Characteristic signs:
-
-| Observation | Detail |
-|---|---|
-| Control appears below visible area | `Bounds.Bottom` greatly exceeds `ClientRectangle.Height` |
-| Problem is DPI-dependent | Invisible at 100% DPI, severe at 200%+ |
-| Problem is already present before `Show()` | The bad position is set during form construction |
-| The stale-anchor bug is .NET 8+ specific | The bad positive anchor capture does not occur under .NET Framework 4.8 |
-
-### Reproduction
-
-The displacement is proportional to the DPI scale factor. At 200% DPI, the child control's
-coordinates are already DPI-scaled at the time of anchor capture, but the parent's
-`DisplayRectangle` is still at a transient construction-time size. For example:
-
-```
-parent ClientRectangle.Height = 100 ← transient height during construction
-control bounds (DPI-scaled) = {Y=284, Height=176}
-bottomOffset = (284 + 176) − 100 = +360 ← positive: stale capture detected
-→ final Y = finalParentHeight + 360 − 176 ← control far below visible area
-```
-
-The transient parent height occurs when a child control is added to a container **after** that
-container has been inserted into a deeper hierarchy, but before any top-level form is attached.
-At that moment the layout engine assigns a temporary, too-small height to the container, and the
-V1 path captures the anchor offset against that height.
-
----
-
-## Root Cause
-
-### The Stable Behavior in .NET Framework 4.8
-
-In .NET Framework, the `EnableAnchorLayoutHighDpiImprovements` switch defaults to `false`. The
-legacy `ComputeAnchoredBounds` path always stores `Right` and `Bottom` as **negative distances**
-from the parent's edges:
-
-```
-anchorInfo.Right = elementBounds.Right - parentWidth // e.g. −20 (20 px from right edge)
-anchorInfo.Bottom = elementBounds.Bottom - parentHeight // e.g. −12 (12 px from bottom edge)
-```
-
-These negative offsets are stable regardless of when they are captured and are simply added to
-the live `displayRect` dimensions at layout time, placing the control correctly.
-
-### The Broken Path in .NET 8+
-
-`EnableAnchorLayoutHighDpiImprovements` no longer exists. The DPI-aware branch is now gated on
-`ScaleHelper.IsScalingRequirementMet`, which returns `true` whenever any monitor is above 96 dpi
-(100%) or the process is per-monitor-aware:
-
-```csharp
-// ScaleHelper.cs
-internal static bool IsScalingRequirementMet => IsScalingRequired || s_processPerMonitorAware;
-internal static bool IsScalingRequired => InitialSystemDpi != OneHundredPercentLogicalDpi;
-```
-
-When `IsScalingRequirementMet` is `true`, `UpdateAnchorInfo` (V1) takes a special branch intended
-to rescue controls that were pushed off-screen by a DPI-scale event:
-
-```csharp
-if (IsAnchored(anchor, AnchorStyles.Right))
-{
- if (ScaleHelper.IsScalingRequirementMet
- && (anchorInfo.Right - parentWidth > 0) // control appears beyond right edge
- && (oldAnchorInfo.Right < 0)) // but had a valid negative anchor before
- {
- // Preserve old anchor to avoid losing the control off the right edge.
- anchorInfo.Right = oldAnchorInfo.Right;
- }
- else
- {
- anchorInfo.Right -= parentWidth; // standard negative-distance calculation
- }
-}
-```
-
-**The guard fires incorrectly during initial form construction.** When a child control is added
-while its parent already has a transient (too-small) `DisplayRectangle`, these conditions hold:
-
-1. The parent height is transiently small (e.g. `100`).
-2. The control's `Y` coordinate is already DPI-scaled (e.g. `284` at 200%).
-3. `anchorInfo.Bottom - parentHeight = (284 + 176) − 100 = +360 > 0` — the guard fires.
-4. `oldAnchorInfo` is also invalid, so the preserved offset is wrong.
-
-On the next layout pass, this wrong positive offset is applied against the final (larger) parent
-height, displacing the control far outside the visible area.
-
-### What the Compat Repair Fixes
-
-The compat repair in `ComputeAnchoredBounds` detects a stale positive trailing anchor and
-recomputes it against a stable baseline. After the repair, the stale positive `Bottom` (or
-`Right`) value is converted back into a stable negative margin. The control is then placed
-correctly relative to the parent's final size.
-
-### The Stretch-Anchor Double-Refresh
-
-A second failure can occur on the same layout pass when a container is itself stretch-anchored
-(`Top | Bottom` or `Left | Right`):
-
-1. The stretch anchor is recovered against the growth in the parent display rectangle.
-2. After recovery, the `Bottom` (or `Right`) value is positive by design for a stretch-anchored
- control — satisfying the stale-positive check.
-3. A second refresh would then overwrite the recovered stretch anchors, shrinking the container
- back down.
-
-This is not a separate layout system defect. It is the result of the compat repair firing twice
-with different semantics on the same axis. The fix evaluates the stretch check first and
-short-circuits the positive check when the stretch path fires (see Solution 3).
-
-### Why High DPI Amplifies the Error
-
-At 200% DPI all coordinates are approximately doubled. A moderate anchor offset error (e.g. +180)
-becomes a large one (+360), pushing the control well outside any visible area. At 100% DPI the
-same error is small enough to be nearly invisible.
-
-### Contributing Factors Summary
-
-| Factor | Role |
-|---|---|
-| `ScaleHelper.IsScalingRequirementMet` | Activates the DPI-aware branch for any non-100% DPI monitor |
-| `UpdateAnchorInfo` V1 guard condition | Fires incorrectly during construction against a transient parent size |
-| `ControlDpiScalingHelper` coordinate scaling | Multiplies all coordinates by the DPI factor, amplifying the captured error |
-| Stretch-anchor follow-up refresh | Reapplies stale-positive logic on an axis already recovered as a stretch anchor, shrinking the parent again |
-
----
-
-## Solutions
-
-Three independent approaches exist, ordered from most to least recommended.
-
-### Decision Guide
-
-```
-Are you on .NET 8+ and can change the app configuration?
- └─ Yes → Solution 1 (AnchorLayoutV2) — eliminates the root cause, zero code change
-Are you unable to change configuration (e.g. third-party host)?
- └─ Solution 3 (fork additive repair) — no app change required, smallest source delta
-Do you need a workaround with no source changes at all?
- └─ Solution 2 (DpiUnaware) — loses DPI sharpness on scaled monitors
-```
-
----
-
-### Solution 1 — Enable `AnchorLayoutV2` ✅ Recommended
-
-`AnchorLayoutV2` (introduced in .NET 8, opt-in) replaces the over-eager V1 capture with a
-**deferred model**: anchor offsets are not committed until the parent's layout is resumed and its
-`DisplayRectangle` is stable. This eliminates the transient-capture problem at the source.
-
-Enable it in `runtimeconfig.template.json` (or `runtimeconfig.json`):
-
-```json
-{
- "configProperties": {
- "System.Windows.Forms.AnchorLayoutV2": true
- }
-}
-```
-
-| Attribute | Value |
-|---|---|
-| Risk | Low — opt-in, no source change required |
-| Scope | Per-application configuration |
-| Verified | Yes — eliminates the transient-capture path entirely |
-| Limitation | Requires the application to control its own `runtimeconfig` |
-
----
-
-### Solution 2 — Use `DpiUnaware` or `DpiUnawareGdiScaled`
-
-Setting the DPI mode to `DpiUnaware` (or `DpiUnawareGdiScaled`) makes
-`IsScalingRequirementMet` return `false`, so .NET 8 follows the same stable anchor path as
-.NET Framework.
-
-```csharp
-Application.SetHighDpiMode(HighDpiMode.DpiUnaware);
-// or
-Application.SetHighDpiMode(HighDpiMode.DpiUnawareGdiScaled);
-```
-
-| Attribute | Value |
-|---|---|
-| Risk | Low — well-understood mode |
-| Scope | Per-application startup |
-| Verified | Yes — sidesteps the broken branch entirely |
-| Limitation | UI appears blurry on scaled monitors (GDI bitmap stretching) |
-
----
-
-### Solution 3 — Additive Layout-Side Repair ⭐ Current Fork Implementation
-
-This is the **current implementation** in the WiseTech Global WinForms fork. It preserves the
-original `UpdateAnchorInfo` behavior entirely and **adds a compat repair step** inside
-`ComputeAnchoredBounds` that handles both stale positive trailing anchors and stale stretch-anchor
-recovery when the parent's `DisplayRectangle` has grown to a stable size.
-
-**Key design principle:** additive and non-breaking — the existing V1 behavior is unchanged.
-
-The single call site in `DefaultLayout.cs`, inside `ComputeAnchoredBounds`, is:
-
-```csharp
-// DefaultLayout.cs — ComputeAnchoredBounds
-AnchorInfo layout = GetAnchorInfo(element)!;
-
-// ... read left/top/right/bottom from layout ...
-
-AnchorStyles anchor = TryRefreshAnchorInfoForDisplayRectangleGrowth(element, layout, displayRect);
-```
-
-All compat logic lives in `DefaultLayout.AnchorLayoutCompat.cs` (a partial class file) to keep the
-change isolated from the main layout file:
-
-```csharp
-// DefaultLayout.AnchorLayoutCompat.cs
-private static AnchorStyles TryRefreshAnchorInfoForDisplayRectangleGrowth(
- IArrangedElement element, AnchorInfo anchorInfo, Rectangle displayRect)
-{
- AnchorStyles anchor = GetAnchor(element);
- Rectangle bounds = GetCachedBounds(element);
-
- // Stretch check must run first. If it triggers, the positive check is skipped so that
- // the recovered stretch anchors are not immediately overwritten by a second refresh.
- bool shouldRefreshStretchAnchors =
- ShouldRefreshAnchorInfoForStaleStretchAnchors(element, anchorInfo, bounds, displayRect, anchor);
-
- if (!shouldRefreshStretchAnchors
- && !ShouldRefreshAnchorInfoForStalePositiveAnchors(anchorInfo, bounds, displayRect, anchor))
- {
- return anchor;
- }
-
- RefreshAnchorInfoForDisplayRectangleGrowth(element, anchorInfo, displayRect, anchor, shouldRefreshStretchAnchors);
-
- return anchor;
-}
-```
-
-**How the repair works:**
-
-- `ShouldRefreshAnchorInfoForStalePositiveAnchors(AnchorInfo, Rectangle bounds, Rectangle displayRect, AnchorStyles)`
- targets controls anchored only to trailing edges (`Right` without `Left`, `Bottom` without
- `Top`). A positive trailing offset on such an axis — combined with evidence that the parent's
- `displayRect` has grown past the recorded `anchorInfo.DisplayRectangle` — is the signature of a
- stale transient capture.
-- `ShouldRefreshAnchorInfoForStaleStretchAnchors(IArrangedElement, AnchorInfo, Rectangle bounds, Rectangle displayRect, AnchorStyles)`
- covers the complementary case: a stretch-anchored control (`Left | Right` or `Top | Bottom`)
- whose actual size is smaller than the size predicted by the specified bounds and the growth in
- the parent display rectangle. It uses `GetDisplayRectangleForSpecifiedContainerBounds` to
- reconstruct a stable reference rectangle from the container's `SpecifiedBounds`.
-- The **stretch check runs before the positive check** (short-circuit `&&`). This is the key
- guard against the double-refresh: once the stretch path fires for an axis, the positive path
- cannot fire for the same axis in the same layout pass.
-- `RefreshAnchorInfoForDisplayRectangleGrowth` delegates to `ResetAnchorInfo`, passing either the
- original captured `DisplayRectangle` (stretch recovery) or a rectangle derived from the
- container's `SpecifiedBounds` (trailing-edge recovery), then recomputes all four anchor values
- so the trailing offsets become stable negative distances again.
-
-| Attribute | Value |
-|---|---|
-| Risk | Low — additive only, no existing behavior changed |
-| Scope | Framework source change (no app change required) |
-| Verified | Stale-anchor and stretch-anchor regression tests pass on `net10.0-windows` at 200% DPI |
-| Limitation | Only targets the transient-capture class of failure; does not address other DPI layout edge cases |
-
----
-
-## Relationship Between Paths
-
-```
-.NET Framework 4.8
- └─ EnableAnchorLayoutHighDpiImprovements = false (disabled by default)
- └─ Always uses legacy ComputeAnchoredBounds — stable, no DPI-aware guard
-
-.NET 8+ (default, DPI > 100%)
- └─ IsScalingRequirementMet = true
- └─ V1 UpdateAnchorInfo with DPI-aware guard — fires spuriously during construction
- ├─ Solution 2: make IsScalingRequirementMet false (DpiUnaware)
- └─ Solution 3: additive layout-side repair in ComputeAnchoredBounds ← current fork
-
-.NET 8+ with AnchorLayoutV2 = true
- └─ UpdateAnchorInfoV2 — defers capture until parent layout is stable
- └─ Solution 1: root-cause fix, no source change needed
-```
-
----
-
-## Verification
-
-### Manual Testing
-
-Requires a machine with at least one monitor set to 200% DPI:
-
-1. Launch the application and open a form with bottom/right-anchored controls inside nested containers.
-2. **Without any fix:** the control is far below its parent or invisible.
-3. **With Solution 1** (`AnchorLayoutV2`): control is correctly positioned inside the parent's client area.
-4. **With Solution 2** (`DpiUnaware`): control is correctly positioned, but UI may appear blurry.
-5. **With Solution 3** (fork repair): control is correctly positioned; test host requires no config change.
-
-### Automated Test Criteria
-
-For a bottom/right-anchored control inside a nested container at 200% DPI:
-
-- The control's `Bounds` are fully contained within the parent's `ClientRectangle` after `Show()`.
-- The control does not overlap sibling controls that were correctly sized.
-- For stretch-anchored containers: the container's height matches the size implied by the
- specified bounds and the display-rectangle growth — it is not collapsed by a second refresh.
-
-### Fork Regression Tests
-
-| Test | Status |
-|---|---|
-| `...V1Path_ShouldRemainWithinGroupBoxAfterFormIsShown` | ✅ trailing-edge stale capture (Solution 3) |
-| `...WithAnchorLayoutV2_RemainsWithinGroupBoxAfterFormIsShown` | ✅ V2 path unaffected |
-| `...StretchAnchoredGroupBox_RecoveredStretchAnchor_ShouldNotTriggerFollowUpPositiveRefresh` | ✅ stretch double-refresh guard |
-
-No changes to `Control.cs` are required — the fix is entirely within `DefaultLayout.cs` and
-`DefaultLayout.AnchorLayoutCompat.cs`.
-
----
-
-## References
-
-- [Anchor layout changes in .NET 8.0](https://github.com/dotnet/winforms/blob/main/docs/design/anchor-layout-changes-in-net80.md)
-- `src/System.Windows.Forms/System/Windows/Forms/Layout/DefaultLayout.cs` — `UpdateAnchorInfo`, `ComputeAnchoredBounds`, `UpdateAnchorInfoV2`
-- `src/System.Windows.Forms/System/Windows/Forms/Layout/DefaultLayout.AnchorLayoutCompat.cs` — `TryRefreshAnchorInfoForDisplayRectangleGrowth`, `ShouldRefreshAnchorInfoForStalePositiveAnchors`, `ShouldRefreshAnchorInfoForStaleStretchAnchors`, `RefreshAnchorInfoForDisplayRectangleGrowth`, `ResetAnchorInfo`
-- `src/System.Windows.Forms.Primitives/src/System/Windows/Forms/Internals/ScaleHelper.cs` — `IsScalingRequirementMet`
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
deleted file mode 100644
index e4e030ff3c2..00000000000
--- a/src/System.Windows.Forms/System/Windows/Forms/Layout/DefaultLayout.AnchorLayoutCompat.cs
+++ /dev/null
@@ -1,338 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Drawing;
-
-namespace System.Windows.Forms.Layout;
-
-internal partial class DefaultLayout
-{
- private const int MinimumSignificantStretchDelta = 16;
-
- private static AnchorStyles TryRefreshAnchorInfoForDisplayRectangleGrowth(IArrangedElement element, AnchorInfo anchorInfo, Rectangle displayRect)
- {
- AnchorStyles anchor = GetAnchor(element);
- Rectangle bounds = GetCachedBounds(element);
- bool shouldRefreshStretchAnchors = ShouldRefreshAnchorInfoForStaleStretchAnchors(
- element,
- anchorInfo,
- bounds,
- displayRect,
- anchor,
- out bool useSpecifiedDisplayRectangleForStretchRefresh);
- bool shouldRefreshPositiveAnchors = ShouldRefreshAnchorInfoForStalePositiveAnchors(anchorInfo, bounds, displayRect, anchor);
-
- if (!shouldRefreshStretchAnchors
- && !shouldRefreshPositiveAnchors)
- {
- return anchor;
- }
-
- RefreshAnchorInfoForDisplayRectangleGrowth(
- element,
- anchorInfo,
- displayRect,
- anchor,
- shouldRefreshStretchAnchors,
- useSpecifiedDisplayRectangleForStretchRefresh);
-
- return anchor;
- }
-
- internal static bool ShouldRefreshAnchorInfoForStalePositiveAnchors(AnchorInfo anchorInfo, Rectangle bounds, Rectangle displayRect, AnchorStyles anchor)
- {
- bool hasStaleRightAnchor = IsAnchored(anchor, AnchorStyles.Right)
- && !IsAnchored(anchor, AnchorStyles.Left)
- && anchorInfo.Right > 0
- && (bounds.Right - displayRect.X <= displayRect.Width
- || displayRect.Width > anchorInfo.DisplayRectangle.Width);
-
- bool hasStaleBottomAnchor = IsAnchored(anchor, AnchorStyles.Bottom)
- && !IsAnchored(anchor, AnchorStyles.Top)
- && anchorInfo.Bottom > 0
- && (bounds.Bottom - displayRect.Y <= displayRect.Height
- || displayRect.Height > anchorInfo.DisplayRectangle.Height);
-
- return hasStaleRightAnchor || hasStaleBottomAnchor;
- }
-
- private static bool ShouldRefreshAnchorInfoForStaleStretchAnchors(
- IArrangedElement element,
- AnchorInfo anchorInfo,
- Rectangle bounds,
- Rectangle displayRect,
- AnchorStyles anchor,
- out bool useSpecifiedDisplayRectangleForStretchRefresh)
- {
- useSpecifiedDisplayRectangleForStretchRefresh = false;
-
- if (!ScaleHelper.IsScalingRequirementMet || element.Container is not { } container)
- {
- return false;
- }
-
- Rectangle specifiedContainerBounds = CommonProperties.GetSpecifiedBounds(container);
- Rectangle specifiedElementBounds = CommonProperties.GetSpecifiedBounds(element);
-
- if (!HasValidSpecifiedBounds(specifiedContainerBounds, specifiedElementBounds))
- {
- return false;
- }
-
- Rectangle specifiedDisplayRect = GetDisplayRectangleForSpecifiedContainerBounds(container, displayRect, specifiedContainerBounds);
- bool hasDisplayRectangleGrowth = HasDisplayRectangleGrowth(anchorInfo.DisplayRectangle, displayRect);
-
- int effectiveSpecifiedWidth = GetEffectiveSpecifiedSizeForStretchRefresh(
- anchorInfo.DisplayRectangle.Width,
- anchorInfo.Left,
- anchorInfo.Right,
- specifiedElementBounds.Width);
- int effectiveSpecifiedHeight = GetEffectiveSpecifiedSizeForStretchRefresh(
- anchorInfo.DisplayRectangle.Height,
- anchorInfo.Top,
- anchorInfo.Bottom,
- specifiedElementBounds.Height);
-
- if (hasDisplayRectangleGrowth
- && IsSpecifiedDisplayRectangleLikelyStale(specifiedDisplayRect, anchorInfo.DisplayRectangle))
- {
- return false;
- }
-
- bool hasStaleHorizontalStretchAnchor = IsAnchored(anchor, AnchorStyles.Left)
- && IsAnchored(anchor, AnchorStyles.Right)
- && anchorInfo.Right < 0
- && hasDisplayRectangleGrowth
- && IsProjectedStretchAnchorSmallerThanExpected(
- displayRect.Width,
- anchorInfo.Left,
- anchorInfo.Right,
- effectiveSpecifiedWidth,
- specifiedDisplayRect.Width);
-
- bool hasStaleVerticalStretchAnchor = IsAnchored(anchor, AnchorStyles.Top)
- && IsAnchored(anchor, AnchorStyles.Bottom)
- && anchorInfo.Bottom < 0
- && hasDisplayRectangleGrowth
- && IsProjectedStretchAnchorSmallerThanExpected(
- displayRect.Height,
- anchorInfo.Top,
- anchorInfo.Bottom,
- effectiveSpecifiedHeight,
- specifiedDisplayRect.Height);
-
- bool hasOversizedHorizontalStretchAnchor = IsAnchored(anchor, AnchorStyles.Left)
- && IsAnchored(anchor, AnchorStyles.Right)
- && anchorInfo.Right > 0
- && IsLargerThanExpectedStretchedSize(bounds.Width, specifiedElementBounds.Width, displayRect.Width, specifiedDisplayRect.Width);
-
- bool hasOversizedVerticalStretchAnchor = IsAnchored(anchor, AnchorStyles.Top)
- && IsAnchored(anchor, AnchorStyles.Bottom)
- && anchorInfo.Bottom > 0
- && IsLargerThanExpectedStretchedSize(bounds.Height, specifiedElementBounds.Height, displayRect.Height, specifiedDisplayRect.Height);
-
- useSpecifiedDisplayRectangleForStretchRefresh = hasOversizedHorizontalStretchAnchor || hasOversizedVerticalStretchAnchor;
-
- return hasStaleHorizontalStretchAnchor
- || hasStaleVerticalStretchAnchor
- || hasOversizedHorizontalStretchAnchor
- || hasOversizedVerticalStretchAnchor;
- }
-
- internal static void RefreshAnchorInfoForDisplayRectangleGrowth(
- IArrangedElement element,
- AnchorInfo anchorInfo,
- Rectangle displayRect,
- AnchorStyles anchor,
- bool isStretchAnchorRefresh,
- bool useSpecifiedDisplayRectangleForStretchRefresh = false)
- {
- Rectangle cachedElementBounds = GetCachedBounds(element);
- Rectangle elementBounds = cachedElementBounds;
- Rectangle anchorDisplayRect = displayRect;
- Rectangle originalDisplayRect = anchorInfo.DisplayRectangle;
-
- if (element.Container is { } container)
- {
- Rectangle specifiedContainerBounds = CommonProperties.GetSpecifiedBounds(container);
- Rectangle specifiedElementBounds = CommonProperties.GetSpecifiedBounds(element);
-
- if (HasValidSpecifiedBounds(specifiedContainerBounds, specifiedElementBounds))
- {
- anchorDisplayRect = isStretchAnchorRefresh && !useSpecifiedDisplayRectangleForStretchRefresh
- ? originalDisplayRect
- : GetDisplayRectangleForSpecifiedContainerBounds(container, displayRect, specifiedContainerBounds);
-
- bool useCachedBoundsForStretchRefresh = isStretchAnchorRefresh
- && ShouldUseCachedBoundsForStretchRefresh(anchor, cachedElementBounds, specifiedElementBounds, anchorDisplayRect);
-
- elementBounds = useCachedBoundsForStretchRefresh
- ? cachedElementBounds
- : specifiedElementBounds;
- }
- }
-
- ResetAnchorInfo(anchorInfo, elementBounds, anchorDisplayRect, anchor);
- }
-
- private static bool ShouldUseCachedBoundsForStretchRefresh(
- AnchorStyles anchor,
- Rectangle cachedElementBounds,
- Rectangle specifiedElementBounds,
- Rectangle anchorDisplayRect)
- {
- bool isHorizontalStretch = IsAnchored(anchor, AnchorStyles.Left)
- && IsAnchored(anchor, AnchorStyles.Right);
- bool isVerticalStretch = IsAnchored(anchor, AnchorStyles.Top)
- && IsAnchored(anchor, AnchorStyles.Bottom);
-
- bool specifiedIsSignificantlyWiderThanDisplay = specifiedElementBounds.Width > anchorDisplayRect.Width + GetSignificantStretchDelta(anchorDisplayRect.Width);
- bool specifiedIsSignificantlyTallerThanDisplay = specifiedElementBounds.Height > anchorDisplayRect.Height + GetSignificantStretchDelta(anchorDisplayRect.Height);
- bool cachedWidthFitsDisplay = cachedElementBounds.Width <= anchorDisplayRect.Width + 1;
- bool cachedHeightFitsDisplay = cachedElementBounds.Height <= anchorDisplayRect.Height + 1;
-
- return (isHorizontalStretch && specifiedIsSignificantlyWiderThanDisplay && cachedWidthFitsDisplay)
- || (isVerticalStretch && specifiedIsSignificantlyTallerThanDisplay && cachedHeightFitsDisplay);
- }
-
- private static int GetSignificantStretchDelta(int size)
- {
- // Ignore tiny differences caused by borders/rounding, but treat large drifts as stale metadata.
- return Math.Max(MinimumSignificantStretchDelta, size / MinimumSignificantStretchDelta);
- }
-
- private static void ResetAnchorInfo(AnchorInfo anchorInfo, Rectangle elementBounds, Rectangle displayRect, AnchorStyles anchor)
- {
- anchorInfo.DisplayRectangle = displayRect;
- anchorInfo.Left = elementBounds.Left - displayRect.X;
- anchorInfo.Top = elementBounds.Top - displayRect.Y;
- anchorInfo.Right = elementBounds.Right - displayRect.X;
- anchorInfo.Bottom = elementBounds.Bottom - displayRect.Y;
-
- if (IsAnchored(anchor, AnchorStyles.Right))
- {
- anchorInfo.Right -= displayRect.Width;
-
- if (!IsAnchored(anchor, AnchorStyles.Left))
- {
- anchorInfo.Left -= displayRect.Width;
- }
- }
- else if (!IsAnchored(anchor, AnchorStyles.Left))
- {
- anchorInfo.Right -= displayRect.Width / 2;
- anchorInfo.Left -= displayRect.Width / 2;
- }
-
- if (IsAnchored(anchor, AnchorStyles.Bottom))
- {
- anchorInfo.Bottom -= displayRect.Height;
-
- if (!IsAnchored(anchor, AnchorStyles.Top))
- {
- anchorInfo.Top -= displayRect.Height;
- }
- }
- else if (!IsAnchored(anchor, AnchorStyles.Top))
- {
- anchorInfo.Bottom -= displayRect.Height / 2;
- anchorInfo.Top -= displayRect.Height / 2;
- }
- }
-
- private static bool HasValidSpecifiedBounds(Rectangle specifiedContainerBounds, Rectangle specifiedElementBounds)
- {
- return specifiedContainerBounds.Width > 0
- && specifiedContainerBounds.Height > 0
- && specifiedElementBounds.Width > 0
- && specifiedElementBounds.Height > 0;
- }
-
- private static bool HasDisplayRectangleGrowth(Rectangle originalDisplayRect, Rectangle currentDisplayRect)
- {
- return currentDisplayRect.Width > originalDisplayRect.Width
- || currentDisplayRect.Height > originalDisplayRect.Height;
- }
-
- private static bool IsSmallerThanExpectedStretchedSize(int actualSize, int specifiedSize, int currentDisplaySize, int specifiedDisplaySize)
- {
- if (specifiedSize <= 0 || currentDisplaySize <= 0 || specifiedDisplaySize <= 0)
- {
- return false;
- }
-
- int expectedSize = specifiedSize + Math.Max(0, currentDisplaySize - specifiedDisplaySize);
-
- // Allow 1-pixel tolerance for integer rounding.
- return actualSize + 1 < expectedSize;
- }
-
- private static bool IsProjectedStretchAnchorSmallerThanExpected(
- 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 IsSmallerThanExpectedStretchedSize(projectedSize, specifiedSize, currentDisplaySize, specifiedDisplaySize);
- }
-
- private static int GetEffectiveSpecifiedSizeForStretchRefresh(
- int anchorDisplaySize,
- int leadingAnchor,
- int trailingAnchor,
- int specifiedSize)
- {
- if (anchorDisplaySize <= 0 || specifiedSize <= 0)
- {
- return specifiedSize;
- }
-
- int baselineProjectedSize = Math.Max(0, anchorDisplaySize + trailingAnchor - leadingAnchor);
- int staleSpecifiedDelta = GetSignificantStretchDelta(baselineProjectedSize);
-
- return specifiedSize > baselineProjectedSize + staleSpecifiedDelta
- ? baselineProjectedSize
- : specifiedSize;
- }
-
- private static bool IsLargerThanExpectedStretchedSize(int actualSize, int specifiedSize, int currentDisplaySize, int specifiedDisplaySize)
- {
- if (specifiedSize <= 0 || currentDisplaySize <= 0 || specifiedDisplaySize <= 0)
- {
- return false;
- }
-
- int expectedSize = Math.Max(0, specifiedSize + currentDisplaySize - specifiedDisplaySize);
-
- // Allow 1-pixel tolerance for integer rounding.
- return actualSize > expectedSize + 1;
- }
-
- private static bool IsSpecifiedDisplayRectangleLikelyStale(Rectangle specifiedDisplayRect, Rectangle anchorDisplayRect)
- {
- int widthDelta = GetSignificantStretchDelta(anchorDisplayRect.Width);
- int heightDelta = GetSignificantStretchDelta(anchorDisplayRect.Height);
-
- return specifiedDisplayRect.Width > anchorDisplayRect.Width + widthDelta
- || specifiedDisplayRect.Height > anchorDisplayRect.Height + heightDelta;
- }
-
- private static Rectangle GetDisplayRectangleForSpecifiedContainerBounds(IArrangedElement container, Rectangle displayRect, Rectangle specifiedContainerBounds)
- {
- int nonClientWidth = Math.Max(0, container.Bounds.Width - displayRect.Width);
- int nonClientHeight = Math.Max(0, container.Bounds.Height - displayRect.Height);
-
- int displayWidth = Math.Max(0, specifiedContainerBounds.Width - nonClientWidth);
- int displayHeight = Math.Max(0, specifiedContainerBounds.Height - nonClientHeight);
-
- return new Rectangle(displayRect.X, displayRect.Y, displayWidth, displayHeight);
- }
-}
diff --git a/src/System.Windows.Forms/System/Windows/Forms/Layout/DefaultLayout.cs b/src/System.Windows.Forms/System/Windows/Forms/Layout/DefaultLayout.cs
index b0b05183a93..3336f4938d4 100644
--- a/src/System.Windows.Forms/System/Windows/Forms/Layout/DefaultLayout.cs
+++ b/src/System.Windows.Forms/System/Windows/Forms/Layout/DefaultLayout.cs
@@ -242,7 +242,7 @@ private static Rectangle ComputeAnchoredBounds(IArrangedElement element, Rectang
int right = layout.Right + displayRect.X;
int bottom = layout.Bottom + displayRect.Y;
- AnchorStyles anchor = TryRefreshAnchorInfoForDisplayRectangleGrowth(element, layout, displayRect);
+ AnchorStyles anchor = GetAnchor(element);
if (IsAnchored(anchor, AnchorStyles.Right))
{
@@ -729,15 +729,6 @@ private static void UpdateAnchorInfo(IArrangedElement element)
SetAnchorInfo(element, anchorInfo);
}
- Rectangle cachedBounds = GetCachedBounds(element);
- AnchorInfo oldAnchorInfo = new()
- {
- Left = anchorInfo.Left,
- Top = anchorInfo.Top,
- Right = anchorInfo.Right,
- Bottom = anchorInfo.Bottom
- };
-
Rectangle elementBounds = element.Bounds;
anchorInfo.Left = elementBounds.Left;
anchorInfo.Top = elementBounds.Top;
@@ -748,10 +739,6 @@ private static void UpdateAnchorInfo(IArrangedElement element)
int parentWidth = parentDisplayRect.Width;
int parentHeight = parentDisplayRect.Height;
- // Keep the display rectangle that produced these V1 anchors so the compat refresh path
- // can detect when the parent later grows and the stored positive right/bottom anchors are stale.
- anchorInfo.DisplayRectangle = parentDisplayRect;
-
// The anchors is relative to the parent DisplayRectangle, so offset the anchors
// by the DisplayRect origin
anchorInfo.Left -= parentDisplayRect.X;
@@ -762,24 +749,12 @@ private static void UpdateAnchorInfo(IArrangedElement element)
AnchorStyles anchor = GetAnchor(element);
if (IsAnchored(anchor, AnchorStyles.Right))
{
- if (ScaleHelper.IsScalingRequirementMet && (anchorInfo.Right - parentWidth > 0) && (oldAnchorInfo.Right < 0))
- {
- // Parent was resized to fit its parent, or screen, we need to reuse old anchors info to prevent losing control beyond right edge.
- anchorInfo.Right = oldAnchorInfo.Right;
- if (!IsAnchored(anchor, AnchorStyles.Left))
- {
- // Control might have been resized, update Left anchors.
- anchorInfo.Left = oldAnchorInfo.Right - cachedBounds.Width;
- }
- }
- else
- {
- anchorInfo.Right -= parentWidth;
+ // This differs from the official WinForms implementation because we encountered an issue with DPI handling. See WI01100144.
+ anchorInfo.Right -= parentWidth;
- if (!IsAnchored(anchor, AnchorStyles.Left))
- {
- anchorInfo.Left -= parentWidth;
- }
+ if (!IsAnchored(anchor, AnchorStyles.Left))
+ {
+ anchorInfo.Left -= parentWidth;
}
}
else if (!IsAnchored(anchor, AnchorStyles.Left))
@@ -790,26 +765,12 @@ private static void UpdateAnchorInfo(IArrangedElement element)
if (IsAnchored(anchor, AnchorStyles.Bottom))
{
- if (ScaleHelper.IsScalingRequirementMet && (anchorInfo.Bottom - parentHeight > 0) && (oldAnchorInfo.Bottom < 0))
- {
- // The parent was resized to fit its parent or the screen, we need to reuse the old anchors info
- // to prevent positioning the control beyond the bottom edge.
- anchorInfo.Bottom = oldAnchorInfo.Bottom;
+ // This differs from the official WinForms implementation because we encountered an issue with DPI handling. See WI01100144.
+ anchorInfo.Bottom -= parentHeight;
- if (!IsAnchored(anchor, AnchorStyles.Top))
- {
- // The control might have been resized, update the Top anchor.
- anchorInfo.Top = oldAnchorInfo.Bottom - cachedBounds.Height;
- }
- }
- else
+ if (!IsAnchored(anchor, AnchorStyles.Top))
{
- anchorInfo.Bottom -= parentHeight;
-
- if (!IsAnchored(anchor, AnchorStyles.Top))
- {
- anchorInfo.Top -= parentHeight;
- }
+ anchorInfo.Top -= parentHeight;
}
}
else if (!IsAnchored(anchor, AnchorStyles.Top))