Fix 11214 by removing IsHandleCreated in OnFontChanged
Fixes #11214, #12851
Root Cause
- The reason for this problem is that
PerformAutoScaleis not executed, because when the Form object is created, its handle is not created immediately, and we have added a check in our code that the handle must be created before scaling.
Proposed changes
-Add a check on the default font setting in the OnFontChanged function of ContainerControls.cs to ensure that PerformAutoScale is executed smoothly
Customer Impact
- When the font for the entire application was adjusted, the controls on the form can be scaled appropriately
Risk
- Minimal
Screenshots
Before
When I adjust the font for the entire application, the controls on the form don't scale appropriately
After
The controls on the form can be scaled appropriately after adjust the font for the entire application
Test methodology
- Manually
Test environment(s)
- .net 9.0.0-preview.7.24355.8
Microsoft Reviewers: Open in CodeFlow
Codecov Report
All modified and coverable lines are covered by tests :white_check_mark:
Project coverage is 75.06831%. Comparing base (
10f120d) to head (8af286b). Report is 235 commits behind head on main.
Additional details and impacted files
@@ Coverage Diff @@
## main #11641 +/- ##
===================================================
+ Coverage 74.51776% 75.06831% +0.55055%
===================================================
Files 3040 3059 +19
Lines 629560 632003 +2443
Branches 46839 46782 -57
===================================================
+ Hits 469134 474434 +5300
+ Misses 157058 154190 -2868
- Partials 3368 3379 +11
| Flag | Coverage Δ | |
|---|---|---|
| Debug | 75.06831% <100.00000%> (+0.55055%) |
:arrow_up: |
| integration | 17.93212% <100.00000%> (+0.04159%) |
:arrow_up: |
| production | 48.22199% <100.00000%> (+0.80597%) |
:arrow_up: |
| test | 97.01631% <ø> (+0.06416%) |
:arrow_up: |
| unit | 45.25976% <100.00000%> (+0.71726%) |
:arrow_up: |
Flags with carried forward coverage won't be shown. Click here to find out more.
I'm not sure that this fix addresses all possible cases. Fundamentally, we are missing information about what font/DPI the controls are scaled for right now. This fix assumes that the application was designed for the DefaultFont and that the current sizes are still set to the "initial" sizes. The former assumption is reasonable, the latter is not necessarily true. Maybe the application had been resized a couple of times and the current sizes are not appropriate for the default font anymore. Previous layout design relied on the AutoScaling ratios to maintain the current state as related to the original designed sizes. We would need to either revive that logic or to implement an extention on it. I suggest we revisit this bug in NET10
@LeafShi1 - let's revisit this change in NET10
I'm not sure that this fix addresses all possible cases. Fundamentally, we are missing information about what font/DPI the controls are scaled for right now. This fix assumes that the application was designed for the DefaultFont and that the current sizes are still set to the "initial" sizes. The former assumption is reasonable, the latter is not necessarily true. Maybe the application had been resized a couple of times and the current sizes are not appropriate for the default font anymore. Previous layout design relied on the AutoScaling ratios to maintain the current state as related to the original designed sizes. We would need to either revive that logic or to implement an extention on it. I suggest we revisit this bug in NET10
@Tanya-Solyanik According to customer feedback, this fix can solve their problem. This fix assumes that the font is changed before the handle is created. Once the handle is created, the scaling will be executed according to the original logic.
I tried to change the font several times, setting three different fonts in Form.cs and Form.Designer.cs, and the scaling results were normal.
But there is a question, should we scale the font before the handle is created?
But there is a question, should we scale the font before the handle is created?
We don't have this information at the Handle creation time. In some cases the requested size will be already scaled by the user app and in some it would be the default size. We have Scaling required properties for control sizes, but not for fonts.
But there is a question, should we scale the font before the handle is created?
We don't have this information at the Handle creation time. In some cases the requested size will be already scaled by the user app and in some it would be the default size. We have
Scaling requiredproperties for control sizes, but not for fonts.
Whether the form/control automatically scales should not be linked to the handle
I did the following experiment
- When I set the font for the Form in
winform.designer.cs, the form will scale according to the set font size. At this time, the handle of the form has not been created
The call stack it executes is as follows (Run OnFontChanged first and then run PerformAutoScale):
******OnFontChanged***** at System.Windows.Forms.ContainerControl.OnFontChanged(EventArgs e) at System.Windows.Forms.Form.OnFontChanged(EventArgs e) at System.Windows.Forms.Control.set_Font(Font value) at ScratchProject.Form1.InitializeComponent() at ScratchProject.Form1..ctor() at ScratchProject.Program.Main()
*******PerformAutoScale****** at System.Windows.Forms.ContainerControl.PerformAutoScale(Boolean includedBounds, Boolean excludedBounds, Boolean causedByFontChanged) at System.Windows.Forms.ContainerControl.PerformNeededAutoScaleOnLayout() at System.Windows.Forms.ContainerControl.OnLayoutResuming(Boolean performLayout) at System.Windows.Forms.Control.ResumeLayout(Boolean performLayout) at ScratchProject.Form1.InitializeComponent() at ScratchProject.Form1..ctor() at ScratchProject.Program.Main()
- When I set the font after
InitializeComponent(), the page layout has been determined. At this time, the judgment (IsHandle created) is added, resulting in only the font being changed, and the Form is not scaled
The call stack it executes is as follows (Run PerformAutoScale first and then OnFontChanged):
******PerformAutoScale******* at System.Windows.Forms.ContainerControl.PerformAutoScale(Boolean includedBounds, Boolean excludedBounds, Boolean causedByFontChanged) at System.Windows.Forms.ContainerControl.PerformNeededAutoScaleOnLayout() at System.Windows.Forms.ContainerControl.OnLayoutResuming(Boolean performLayout) at System.Windows.Forms.Control.ResumeLayout(Boolean performLayout) at ScratchProject.Form1.InitializeComponent() at ScratchProject.Form1..ctor()
******OnFontChanged***** at System.Windows.Forms.ContainerControl.OnFontChanged(EventArgs e) at System.Windows.Forms.Form.OnFontChanged(EventArgs e) at System.Windows.Forms.Control.set_Font(Font value) at ScratchProject.Form1..ctor() at ScratchProject.Program.Main() at ScratchProject.Program.Main()
So whether the form automatically scales should not be linked to the handle.
It is indeed inappropriate to add the Font != DefaultFont judgment. As you said, the font may have been changed many times, so delete IsHandleCreated here should be the right solution
What do you think?
Whether the form/control automatically scales should not be linked to the handle
I'm concerned about scaling for the DPI. At the runtime, the form should be scaled according to the DPI of the screen it's created on from the DPI it was designed on. I.e. if the design time DPI scaling is 100%, but the runtime DPI scaling is 200%, form size should be doubled. If the Form uses AutoScaleMode.Dpi, then font should be rescaled. However, we don't always know what on what screen the control is displayed and we might have to scale font after handle is created, when we know the current screen DPI. And we don't know if the default font had been scaled. It is more efficient to scale the default font and have it applied to all controls in the application than to scale font on each container control.
Jeremy is planning to rework the default font handling. So fix for this issue will depend on the new infrastructure that he will put in. Could you please wait with this this change until that work is done?
Jeremy is planning to rework the default font handling. So fix for this issue will depend on the new infrastructure that he will put in. Could you please wait with this change until that work is done?
OK, I will wait for the new infrastructure
Redoing layout is not something we're going to get to for a while. We should look at a targeted fix for .NET 10 here, even if it is behind a compat switch.
Redoing layout is not something we're going to get to for a while. We should look at a targeted fix for .NET 10 here, even if it is behind a compat switch.
@JeremyKuhne Do you mean add an AppContext switch for it?
Redoing layout is not something we're going to get to for a while. We should look at a targeted fix for .NET 10 here, even if it is behind a compat switch.
@JeremyKuhne Do you mean add an AppContext switch for it?
If the change is risky, yes.
You're undoing #5557, which is concerning. Presuming doing this would break the other fixes. Whatever we do here it would be good to know how this impacts the bugs fixed by that change.
You're undoing #5557, which is concerning. Presuming doing this would break the other fixes. Whatever we do here it would be good to know how this impacts the bugs fixed by that change.
This only undoes the changes to ContainerControl in #5557. This undo does not affect the original fix. The test team has verified that the original issues are still fixed.
@JeremyKuhne @KlausLoeffelmann No other impacts have been found so far, should we stick with the original fix and add a switch here or revert the original change?
I have a general problem with the Layout scaling, as a series of "fixes" we did in the past opened up different issues.
What I would like to do, is before we address this, come up with a specific HighDPI Layout Test App, which we should use in some exploratory settings, and then go from there, to investigate some status-quo first.
The question I have is: Do we already have something like this? And if yes, can someone point me to it, so I can test and play with it myself? 😸
@Olina-Zhang: Do you have a test scenario, which would basically run on a physical machine (not a VM) and include the following tests. If not, could you schedule some time, and have someone do the following steps and document the respective results via screenshots?
-
Set the Resolution of the physical machine as high as possible, and set the scaling to 250%
-
Important: Log-off and Log-on again (no reboot necessary).
-
Make sure, you have Visual Studio 2017 and Visual Studio 2022 installed on that machine.
-
Create a new Solution in Visual Studio 2017, Framework 4.6.x
-
Create a Form. Remember the size of the Form, because for the next scenarios, the Forms should all roughly have the same size. This is important!
-
Set the Forms
Text, so it reflects what VS Versions and Framework Version it used (for example"Visual Studio 2017, NetFX 4.6.1". Remember to do that for all the other forms to come. -
Add a
GroupBox, almost full Form's width but only half Form's height, and anchor left, top, right side. -
Add another GroupBox below that GroupBox, again almost full Form's width, half Form's height, anchored left, top, right and bottom.
-
Add a series of Label/TextBox combinations to the first GroupBox like (
First name: [ ]). Make sure, the TextBoxes reach almost the right side of the GroupBox, and are anchored left, top, right. Make sure the TextBoxes are aligned with their labels, and they are aligned with the left sides of the other TextBoxes. -
Do the same for 2nd GroupBox, but use a
TableLayoutPanelto hold the Label/TextBox combinations. Use 40%/60% for the LayoutPanel Columns and AutoHeight for the rows. Make sure, the Margin of the Controls inside of TableLayoutControl are all 5 pixels to each side. -
Add or edit the
app.configfile of the Framework project to include the switch to HighDPI SystemAware. Note: This does not count for the .NET 10 version!
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<System.Windows.Forms.ApplicationConfigurationSection>
<add key="DpiAwareness" value="SystemAware" />
<add key="EnableWindowsFormsHighDpiAutoResizing" value="true" />
</System.Windows.Forms.ApplicationConfigurationSection>
</configuration>
-
Now Create a new Solution in Visual Studio 2022. Add a Framework 4.8.X WinForms Project.
-
Repeat the steps, but do NOT copy any things over from 2017, but please create everything manually again with the Designer. Make sure, that the main Form is roughly the same size in Pixels as the Form from the first scenario.
-
Now, add a .NET 10 WinForms project to the Solution.
-
Repeat the steps YET again, and again, do it only with the Designer. Again, make sure, the Form is roughly the same size, and do not copy anything over.
Now. When you've created all those apps and they are compiling, start them all at the same time, and compare them on the screen. Make individual screenshots, and make one screenshot, which shows all three of them at the same time.
Next: Close all the running Apps and all Visual Studio Instances.
Set the Scaling of the machine back to 100%, but if possible do NOT change the resolution.
Restart the machine
Start VS 2017 and VS 2022, and open all the Forms in the Designer and make screenshots of the Form.
IMPORTANT: Do NOT change the Form or its content, also do not resize the Forms. Just open them and make the Screenshots. You can collapse tool Windows of VS which might be in the way, to get a better view of the Form.
Now, run all the Apps again, and make individual screenshots and one screenshot showing all of them.
Provide all the Screenshots as the test results.
@merriemcgaw, @Olina-Zhang, @LeafShi1 FYI.