Skip to content

Conversation

@dreddy-work
Copy link
Member

@dreddy-work dreddy-work commented Oct 17, 2022

We have multiple issues reported in WinForms around the anchor layout being problematic on higher DPI scale monitors, irrespective of application DPI mode. This document outlines the changes being made in .NET 8.0 to address these issues along with setting the goal to support all supported application DPI modes in WinForms.

Problem in Scope:
Anchored control’s position with respect to its parent should be able to determine at the design time and would only need to be changed if there were explicit changes in the control’s Bounds or when the control is scaled in response to a DPI changed event. Bounds changes as result of Parent’s bounds change shouldn’t alter control’s relative position in the parent’s rectangle. However, layout in WinForms computes the anchored control’s position every time there are changes to control’s bounds or control’s property change that may impact its position. This is leading to the issues we have been seeing. The following is a source snippet that is being serialized in WinForms designer and added comments show the various events that trigger anchor computations and why they could be wrong or unnecessary.

this.button14.Anchor = (System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) // Layout tries to compute anchors with default button14 size and without parent. 
this.button14.Location = new System.Drawing.Point(431, 110); // Layout updates computed anchors for this new bounds, still with default size and no parent.
this.button14.Name = "button14";
this.button14.Size = new System.Drawing.Size(111, 23); // Layout updates computed anchors for this new bounds, still with no parent.
this.Controls.Add(this.button14); // Layout updates computed anchors with parents default size. Which may still be wrong.
this.Size = new System.Drawing.Size(828, 146); // Layout updates computed anchors with parents new size but this size could change based on the Dpi when parent Handle is created.
this.ResumeLayout(false) // Layout updates computed anchors with parents size. Dpi of the screen is still not considered (Handle creations or delayed until control is visible)

The above snippet does not represent the complete set of instances where anchor computations are unnecessary and may hold invalid anchor values. It gets even more complicated when nested UserControls are involved.

Known issues:
We have multiple issues reported here from customers and some of them are direct result of anchor miscalculations. The following are snippets of the category of issues we currently see.

Expected:
image

image

Current Behavior:
image

image

Proposed solution:

Unnecessary control’s anchors computation is the root cause for many issues reported so far. In this proposal, we are delaying the anchors computation to the specific time in the layout flow - which is when the control’s and its parent’s handles are created. By that time, in the majority of scenarios, the control’s and its parent’s bounds are finalized and the display monitor’s DPI has been applied to them. We also have issues with how we are calculating anchors but, in this proposal, we are simplifying anchor calculations as mentioned in the above pic to help diagnose any future issues.
We may still have cases where developers are forced to create handle explicitly, out of order, but those cases can be handled separately by the application developer for any anchor miscalculations. The following are the events we would be using to compute anchors and replacing the current set of events mentioned in Figure 1 above.

  • WmCreate (Create Control)
  • OnParentChanged
  • SetBounds

Source snippet:

private void WmCreate()
{
   .....
   DefaultLayout.UpdateAnchorInfoV2(this);
}

internal static void UpdateAnchorInfoV2(IArrangedElement element)
 {
     if (!LocalAppContextSwitches.EnableAnchorLayoutV2 || !CommonProperties.GetNeedsAnchorLayout(element))
      {
          return;
      }

      Control control = element as Control;
      Debug.Assert(control != null, "AnchorLayoutV2 and beyond are expected to be used only on Control type");

      if (control is null || control.Parent is null)
       {
          return;
       }

      if (!control.IsHandleCreated || !control.Parent.IsHandleCreated)
       {
          return;
       }

      ComputeAnchorInfo(IArrangedElement element)
  }

Simplifying Anchor calculations:
The current anchor calculation implementation is complicated and appears to have been a result of unnecessary attempts to calculate anchors with invalid bounds. Replacing the current implementation with the one described in Figure 1 above. Following is the source snippet that computes the anchors.

private static void ComputeAnchorInfo(IArrangedElement element)
 {
   AnchorInfo? anchorInfo = GetAnchorInfo(element);
   if (anchorInfo is null)
    {
       anchorInfo = new();
       SetAnchorInfo(element, anchorInfo);
    }

    Rectangle displayRect = element.Container.DisplayRectangle;
    Rectangle elementBounds = element.Bounds;

    int x = elementBounds.X;
    int y = elementBounds.Y;

    anchorInfo.Left = x;
    anchorInfo.Top = y;

    anchorInfo.Right = displayRect.Width - (x + elementBounds.Width);
    anchorInfo.Bottom = displayRect.Height - (y + elementBounds.Height);
 }

Risk mitigation:
Layout in general is complex and could impact every component in the WinForms. In order to reduce the potential risk and provide backward compatibility, This changes are quirked under switch System.Windows.Forms.EnableAnchorLayoutV2. These changes are by default on for new/migrating applications targeting .NET 8.0 but the developers can control this by setting the above mentioned flag to false in the runtimeconfig.template.json for the application.
Snippet for runtimeconfig.template.json:

{
  "configProperties": {
    "System.Windows.Forms.EnableAnchorLayoutV2": true
  }
}

Fixes #8090
Fixes #8088

Microsoft Reviewers: Open in CodeFlow

@ghost ghost assigned dreddy-work Oct 17, 2022
@ghost ghost added the draft draft PR label Oct 18, 2022
@RussKie RussKie added the design-discussion Ongoing discussion about design without consensus label Oct 18, 2022
@dreddy-work dreddy-work force-pushed the dev/dreddy/UpdateAnchorLayout branch from 5761470 to 5d85d41 Compare October 20, 2022 19:19
@dreddy-work dreddy-work marked this pull request as ready for review October 20, 2022 19:20
@dreddy-work dreddy-work requested a review from a team as a code owner October 20, 2022 19:20
@dreddy-work dreddy-work added 📖 documentation: breaking please open a breaking change issue https://github.com/dotnet/docs/issues/new?assignees=gewarren and removed design-discussion Ongoing discussion about design without consensus draft draft PR labels Oct 20, 2022
Copy link
Member

@JeremyKuhne JeremyKuhne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly nits. If you can try to update to interpolated strings wherever you're touching code it helps avoid extra work / allocations. They are significantly better everywhere and Debug statements will completely skip forming the string if conditions aren't met.

@ghost ghost added waiting-author-feedback The team requires more information from the author and removed waiting-author-feedback The team requires more information from the author labels Oct 20, 2022
@dreddy-work dreddy-work force-pushed the dev/dreddy/UpdateAnchorLayout branch from 4c746ad to d7bfd19 Compare October 21, 2022 00:37
JeremyKuhne
JeremyKuhne previously approved these changes Oct 21, 2022
@dreddy-work dreddy-work changed the title Update Anchor Layout to support high Dpi machines. Update Anchor Layout to support high DPI machines. Oct 21, 2022
@dreddy-work dreddy-work force-pushed the dev/dreddy/UpdateAnchorLayout branch from 86d8367 to b1c10af Compare October 26, 2022 23:30
@RussKie RussKie added the waiting-author-feedback The team requires more information from the author label Oct 27, 2022
@ghost ghost removed the waiting-author-feedback The team requires more information from the author label Oct 27, 2022
Tanya-Solyanik
Tanya-Solyanik previously approved these changes Oct 27, 2022
@dreddy-work dreddy-work requested a review from RussKie October 27, 2022 22:52
internal static partial class LocalAppContextSwitches
{
// Switch names declared internal below are used in unit/integration tests. Refer to
// https://github.com/microsoft/winforms/blob/tree/main/docs/design/anchor_layout_changes_in_net80.md
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing name to anchor-layout-changes-in-net80.md when renaming it on doc PR. Intentionally underscored here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❗ This link is still incorrect.

@dreddy-work dreddy-work merged commit 168d25b into main Nov 1, 2022
@dreddy-work dreddy-work deleted the dev/dreddy/UpdateAnchorLayout branch November 1, 2022 16:33
@ghost ghost added this to the 8.0 Preview1 milestone Nov 1, 2022
/// <devdoc>
/// Returns AnchorLayoutV2 switch value from runtimeconfig.json. Defaults to true if application is targeting .NET 8.0 and beyond.
/// Refer to
/// https://github.com/microsoft/winforms/blob/tree/main/docs/design/anchor_layout_changes_in_net80.md for more details.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diito

/// </summary>
/// <devdoc>
/// This is the new behavior introduced in .NET 8.0. Refer to
/// https://github.com/microsoft/winforms/blob/tree/main/docs/design/anchor_layout_changes_in_net80.md for more details.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@RussKie
Copy link
Contributor

RussKie commented Nov 2, 2022

A friendly reminder to raise a breaking doc in https://github.com/dotnet/docs/

@dreddy-work
Copy link
Member Author

dotnet/docs#32145

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

📖 documentation: breaking please open a breaking change issue https://github.com/dotnet/docs/issues/new?assignees=gewarren

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Anchored controls layout is broken if Container has visible scrollbars UserCOntrol with anchored controls does not work on high dpi machines.

6 participants