Define a pattern for default values that used to be constants proportional to the default system font.
See original GitHub issuerelated bugs
- https://github.com/dotnet/winforms/issues/8593
- https://github.com/dotnet/winforms/issues/4463 PR - https://github.com/dotnet/winforms/pull/8518
- https://github.com/dotnet/winforms/issues/4461 PR - https://github.com/dotnet/winforms/pull/8474
The background
Some control properties are decorated with a DefaultValueAttribute. When control is created in the designer, and the property had not been initialized yet, property browser reads attribute value and shows it in the property browser. If the user changes such a value in the property browser, design time serialization writes the user-specified value into the InitializeComponent
method in the code-behind file. If the user does not assign specific value to such a property in the property browser, this property will not be serialized into the code-behind file, as controlled on line 1176 by the type description provider. Default value is also used to reset property value in the property browser.
At the runtime, properties that are not assigned to in the InitializeComponent
method, are initialized to default runtime values.
Design time components assume that the runtime default value is the same as the design time one i.e., in the case when the attribute is used, the same as the attribute value. If this assumption is not upheld, the reset
option in the property browser will not work, and InitializeComponent
will explicitly serialize default values instead of letting the control to pick up the runtime default on the initialization.
The issue
In some cases, the default runtime value is dependent on the application font. For example, Height or Width properties. Some of these values had been calculated and hardcoded before we have changed the default font. Currently some of the design time default values defined in the attribute are out of sync with the runtime default values that are explicitly assigned in the constructor.
Example
Design time default value - https://github.com/dotnet/winforms/blob/62edc1ab7b601bee58199fdea2580a54cf5cd25b/src/System.Windows.Forms/src/System/Windows/Forms/DataGridViewRow.cs#L212-L218
Runtime default - https://github.com/dotnet/winforms/blob/62edc1ab7b601bee58199fdea2580a54cf5cd25b/src/System.Windows.Forms/src/System/Windows/Forms/DataGridViewRow.cs#L37
The runtime value is dynamically calculated based on the default font while the designer assumes a constant value.
What happens
- when designer is shown, control is instantiated, and the runtime default value is assigned to the property.
- when control is serialized, TypeDescriptor compares the actual property value (from the runtime default) against the value in the attribute, sees that they are different and adds a line that sets this value to the
InitalizeComponent
. Problem: User had not edited this value and expects that height is picked up from the font, user changes the control font on the startup and the height is not appropriate for the new font.
Suggestion
Use a different pattern to supply the default value, like what ambient properties are already using. See line 1170.
- remove the default value attribute from properties that have dynamic default values.
- add
Reset<Property>
andShouldSerialize<Property>
methods that compare actual value against the dynamic runtime default.
This is a breaking change
Per https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/breaking-change-rules.md#attributes. But I consider this case a grey area because the functionality is preserved, and change is unobservable to the user unless they enumerate the attributes. If they enumerate the attributes, they’ll have to look up the breaking changes docs. If default value functionality is consumed through the TypeDescriptor
infrastructure, for serialization purposes, there will be no change.
A pseudocode representation of the pattern.
before:
namespace System.Windows.Forms;
public class SomeControl : Control
{
private const int DefaultHeight = 22;
public SomeControl()
{
Height = DefaultHeight;
}
[DefaultValue(DefaultHeight)]
public int Height {set; get;}
}
after:
namespace System.Windows.Forms;
public class SomeControl : Control
{
public SomeControl()
{
Height = DefaultHeight;
}
private int DefaultFont { get; }
public int Height
{
// Calculate the height to accommodate text that uses the default font, i.e. use Control.DefaultFont.Height
set; get;
}
private void ResetHeight()
{
Height = DefaultHeight;
}
private bool ShouldSerializeHeight()
{
return Height != DefaultHeight;
}
}
Issue Analytics
- State:
- Created 7 months ago
- Reactions:1
- Comments:9 (9 by maintainers)
Top GitHub Comments
@kirsan31 - sure, I will investigate this bug after the attribute removal is approved. So far, the root cause seems to be that the font is not recognized as the default one.
Video
Looks good as proposed.
We discussed whether the methods
ResetXxx()
andShouldSerializeXxx()
should be public. We concluded that they shouldn’t be because they are indirectly used byTypeDescriptor
, which is the way the designer (and other serializers) should interact with these types.It looks like we might want to consider an analyzer that checks for an assignment of
ItemHeight
and instructs the user to delete the line. Alternatively, the designer could me made aware of which properties are DPI dependent and doesn’t persist those values to hardcoded values into the designer generated code. It sounds like this is planned for a later day.