ContainerControl incorrectly scales multiple times when moving from one ControlCollection to another.
.NET version
.NET 8.0
Did it work in .NET Framework?
Yes
Did it work in any of the earlier releases of .NET Core or .NET 5+?
Sample project appears to work in .NET 6.
Issue description
Setup: The parent form has AutoScaleMode set to DPI, and is being shown on a display with resolution that would cause the form to scale (i.e. not 100%).
Issue: When a ContainerControl is removed from one Controls collection and added to another, the ContainerControl is scaled multiple times (based on the default and inherited fonts?), leading to the size of the control changing each time it is moved.
Notice in the animation below, the size of the TextBox remains static, while the red ContainerControl gets longer.
Steps to reproduce
- Run the sample on a display set above 100%.
- Click button3 on the initial Form2 to pull up the Form4 with the issue.
- Repeatedly click button1 on Form4.
Results: The red ContainerControl gets longer with each button1 click.
Please find the commit that introduced this issue and see if this is controlled by any quirks.
@ChristopherDShea This doesn't seem to be a real problem. It caused by using AutoScaleDimensions = new SizeF(96F, 96F); on non-100% screen
If AutoScaleDimensions uses the automatically generated size( on 150% DPI, the value should be 144X144), there will be no problem.
This is a problem.
The AutoScaleDimensions property on the form is automatically generated by the designer when the form is serialized out. It is used to keep track of the resolution the form was designed in so it can scale values based on the difference between the resolution when serialized out versus when the form is being run at runtime. The AutoScaleDimensions is not defining the resolution the form can be run at, but what is was design at.
It should be fairly commonplace for application to be designed in one resolution while it is run in another as the developer and the end user are often different.
@LeafShi1 I know we did some work post .NET 6 in the Container Controls HDPI space. Can you see if you can find the commit that introduced this and see if we added a quirk around it? I believe we did.
According to the call stack, after executing autoScaleFactor = GetParentAutoScaleFactor(), the returned autoScaleFactor value is different
150% screen:
On .net6.0, the return value of autoScaleFactor should be 1.0X1.0, because the method Scale is be executed
On net10.0, the return value of autoScaleFactor is 1.5X1.5
The Call stack in .net6.0:
System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.GetParentAutoScaleFactor() Line 706 C#
System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.PerformAutoScale(bool includedBounds, bool excludedBounds, bool causedByFontChanged) Line 977 C#
System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.OnFontChanged(System.EventArgs e) Line 898 C#
WinFormsApp8.dll!WinFormsApp8.CustomNumericUpDown.OnFontChanged(System.EventArgs e) Line 35 C#
System.Windows.Forms.dll!System.Windows.Forms.Control.OnParentFontChanged(System.EventArgs e) Line 7480 C#
WinFormsApp8.dll!WinFormsApp8.CustomNumericUpDown.OnParentFontChanged(System.EventArgs e) Line 39 C#
System.Windows.Forms.dll!System.Windows.Forms.Control.OnFontChanged(System.EventArgs e) Line 7348 C#
System.Windows.Forms.dll!System.Windows.Forms.Control.AssignParent(System.Windows.Forms.Control value) Line 4619 C#
The call stack in .net10.0:
System.Windows.Forms.dll!System.Windows.Forms.Control.SetBoundsCore(int x, int y, int width, int height, System.Windows.Forms.BoundsSpecified specified) Line 10090 C#
System.Windows.Forms.dll!System.Windows.Forms.Control.ScaleControl(System.Drawing.SizeF factor, System.Windows.Forms.BoundsSpecified specified) Line 9778 C#
System.Windows.Forms.dll!System.Windows.Forms.ScrollableControl.ScaleControl(System.Drawing.SizeF factor, System.Windows.Forms.BoundsSpecified specified) Line 721 C#
System.Windows.Forms.dll!System.Windows.Forms.Control.ScaleControl(System.Drawing.SizeF includedFactor, System.Drawing.SizeF excludedFactor) Line 9668 C#
System.Windows.Forms.dll!System.Windows.Forms.Control.Scale(System.Drawing.SizeF includedFactor, System.Drawing.SizeF excludedFactor, System.Windows.Forms.Control requestingControl, bool causedByFontChanged) Line 9563 C#
System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.Scale(System.Drawing.SizeF includedFactor, System.Drawing.SizeF excludedFactor, System.Windows.Forms.Control requestingControl, bool causedByFontChanged) Line 1093 C#
System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.PerformAutoScale(bool includedBounds, bool excludedBounds, bool causedByFontChanged) Line 1003 C#
System.Windows.Forms.dll!System.Windows.Forms.ContainerControl.OnFontChanged(System.EventArgs e) Line 893 C#
ScratchProject.dll!ScratchProject.CustomNumericUpDown.OnFontChanged(System.EventArgs e) Line 27 C#
System.Windows.Forms.dll!System.Windows.Forms.Control.OnParentFontChanged(System.EventArgs e) Line 6974 C#
ScratchProject.dll!ScratchProject.CustomNumericUpDown.OnParentFontChanged(System.EventArgs e) Line 32 C#
System.Windows.Forms.dll!System.Windows.Forms.Control.OnFontChanged(System.EventArgs e) Line 6829 C#
System.Windows.Forms.dll!System.Windows.Forms.Control.AssignParent(System.Windows.Forms.Control value) Line 4187 C#
@LeafShi1 - do we have an appcontext switch we could enable to fir the NET10 experience?
@LeafShi1 - do we have an appcontext switch we could enable to fir the NET10 experience?
The only DPI-related switch I can find is System.Windows.Forms.AnchorLayoutV2, but it is useless for this problem.
This problem does not exist on .net6.0 because .net6.0 does not scale the Form according to the runtime DPI
This problem is caused by the font change of the custom control (from the system default font to the font of the parent control), which leads to the call of ContainerControl.PerformAutoScale.
- The scaling ratio of the child control is inherited from the parent control (Form).
- The
autoScaleFactorof the Form is obtained by dividing the actual runtimeScaleDimensions = new SizeF (144F, 144F)by theAutoScaleDimensions = new SizeF (96F, 96F)set at design time.
So once the custom control is moved, its font will be scaled according to 1.5X1.5. The logic here is reasonable.
But this causes the size of the custom control to change from the initial setting Size (200, 23) to (300, 34) when the Form is loaded. When the button is clicked, the size of the custom control changes from Size (300, 34.5) to (450, 51).
To solve this problem, we can provide a fixed font for the custom control.
@Tanya-Solyanik @ChristopherDShea Do you think this solution is feasible?
This issue regressed from PR #5557
And another discovery is that this problem will not reproduce if the secondary screen is used as the first loading screen of the project
-
When the DPI settings of the primary and secondary screens are inconsistent
- The program is run from the secondary screen, the
ContainerControl.OnFontChangedevent is executed, and it is detected that the Handle has been created - When the program is run from the primary screen, the
ContainerControl.OnFontChangedevent is executed, and it is detected that theHandlehas not been created
- The program is run from the secondary screen, the
-
When the DPI settings of the primary and secondary screens are consistent, no matter the program is run from the primary screen or the secondary screen, the ContainerControl.OnFontChanged event is executed and it is detected that the Handle has not been created.
@JeremyKuhne @merriemcgaw Can we consider to revert the PR #5557? It also raises other issue https://github.com/dotnet/winforms/issues/11214, and the original issue #4854 is not fully fixed?
As far as the workaround goes, I had already figured that out and provided it to our customer who reported the problem initially.
As an application developer, the workaround would probably be fine as you know your application, and can apply it for this specific scenario. However as a component-developer, and in this case, our component is the container that is adding and removing the child controls, we have difficulty implementing the workaround. We need to avoid assigning my properties on child controls that we don't revert back. Due to the nature of the Font property, we are unable to determine if the child controls have already had their own font assigned or if it is resolving it from its parent.
@LeafShi1 - reverting something from that long ago has its risks, and any good it introduced would be gone. Chat with @Olina-Zhang about what the original fix was trying to address, and see if it is behind a flag of any kind that we can default to off/false rather than on.