[API Proposal]: Simplify Grid Column and Row Definitions
Background and motivation
Following up on discussion in #5612 and #1739 this proposal is to simplify the definition syntax of RowDefinitions and ColumnDefinitions by:
- Allowing rows and columns within a Grid to be defined by a collection that is delimited by commas or space.
- Creating a Typeconvertor for ColumnDefinitionCollection and RowDefinitionCollection so they can process String as its input.
Goal
The goal of this feature is to make Grid markup less verbose, allowing developers to create grids with simpler syntax.
Example
Current Syntax
Defining columns and rows is overly tedious, repetitive and not very productive, as is shown below:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="300" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="25" />
<RowDefinition Height= "14" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
</Grid>
Proposed Syntax
The same functionality as above with the proposed succinct syntax is shown below:
<Grid ColumnDefinitions="1*, 2*, Auto, *, 300" RowDefinitions="1*, Auto, 25, 14, 20"> </Grid>
<!-- or -->
<Grid ColumnDefinitions="1* 2* Auto * 300" RowDefinitions="1* Auto 25 14 20"> </Grid>
Scope
| Feature | Priority |
|---|---|
| Ability for rows and columns to be defined as a collection or list of values | Must |
| Content properties of RowDefinition and ColumnDefinition are set to Height and Width, respectively | Must |
| Original syntax will still be fully supported and functional | Must |
| Hot Reload will still be fully supported and functional | Must |
| Include support for min/max height/width | Won't |
| Include support for data binding within definitions | Won't |
API Proposal
Provide new typeconverters for ColumnDefinitionCollection and RowDefinitionCollection.
This will allow us to convert string input into the corresponding collection.
namespace System.Windows.Controls
{
+ internal class ColumnDefinitionCollectionConverter : TypeConverter
+ {
+ ...
+ }
+ internal class RowDefinitionCollectionConverter : TypeConverter
+ {
+ ...
+ }
}
Introduce Setter properties for ColumnDefinitons and RowDefinitions of Grid.
+[TypeConverter(typeof(ColumnDefinitionCollectionConverter))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ColumnDefinitionCollection ColumnDefinitions
{
get { ... }
+ set { ... }
}
+[TypeConverter(typeof(RowDefinitionCollectionConverter))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public RowDefinitionCollection RowDefinitions
{
get { ... }
+ set { ... }
}
API Usage
<Grid ColumnDefinitions="1*, 2*, Auto, *, 300" RowDefinitions="1*, Auto, 25, 14, 20"> </Grid>
<Grid ColumnDefinitions="1* 2* Auto * 300" RowDefinitions="1* Auto 25 14 20"> </Grid>
<Grid RowDefinitions="1*, Auto, 25, 14, 20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="300" />
</Grid.ColumnDefinitions>
</Grid>
<Grid ColumnDefinitions="1*, 2*, Auto, *, 300">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="25" />
<RowDefinition Height= "14" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
</Grid>
Alternative Designs
Introduce new public dependency properties Columns and Rows that provide a dedicated place to update the row and column definitions.
+public string Columns
+{
+ get { ... }
+ set { ... }
+}
+public string Rows
+{
+ get { ... }
+ set { ... }
+}
+public static readonly DependencyProperty ColumnsProperty =
+ DependencyProperty.Register(
+ nameof(Columns),
+ typeof(string),
+ typeof(Grid),
+ new FrameworkPropertyMetadata(null, OnColumnsChanged));
+public static readonly DependencyProperty RowsProperty =
+ DependencyProperty.Register(
+ nameof(Rows),
+ typeof(string),
+ typeof(Grid),
+ new FrameworkPropertyMetadata(null, OnRowsChanged));
Remarks
However, this leaves us with two similar properties to set the same things, which is not a clean approach.
Risks
No response
This looks like a reasonably conservative approach, I support the proposed syntax (where the commas are optional like in other collections). The Alternative Design is obviously not viable, not only because of duplication but also because of the ability to change the value once set, which brings challenges of preserving instances of individual definitions etc.
The setter does not need to be public I believe, internal would do. Introducing a public setter brings complexity in terms of managing changes to the value and ownership.
Creating the collections is not that easy though, as they do not have a public constructor and require an instance of the owning Grid. During XAML parsing, the type converter could get the instance of the Grid from ITypeDescriptorContext resp. IProvideValueTarget and could use the internal constructor. However, that means the converters cannot be used on its own without context. Alternatively, we could add public parameter-less constructors and deal with ownership on assignment.
I agree with miloush here; and yeah, it should be possible to specify both comma-separated and space-separated values as it is with other elements, e.g. Thickness.
I agree with miloush here; and yeah, it should be possible to specify both comma-separated and space-separated values as it is with other elements, e.g. Thickness.
@h3xds1nz, @miloush this suggestion is reasonable and I have updated the API Proposal to show both comma-separated and space-separated values.
The setter does not need to be public I believe, internal would do. Introducing a public setter brings complexity in terms of managing changes to the value and ownership.
I am yet to try this one out. Once done I will update the proposal accordingly
Hello Everyone, We are planning to move forward with this API proposal. However, we have noticed that there has been very little activity on this thread. We kindly request you to review the proposal and provide any feedback you may have. Thank you for your attention to this matter. cc @dotnet/dotnet-wpf-triage @etvorun
It might be helpful to post on the original thread for any additional feedback imho, as I'd expect some people subscribed there.
@h3xds1nz as you said, got notified because it was mentioned in #5612 so thx
———
honestly, I don’t know what feedback is to be expected the requested feature is a well established function in winui, maui soooo do it like them, since they are also xaml based?
So I would refer to this issue https://github.com/microsoft/microsoft-ui-xaml/issues/673 https://github.com/xamarin/Xamarin.Forms/issues/7302
for any feedback, and say don’t try to reinvent the wheel do it like they decided to
Yup, didn't know this was here until it was mentioned in #5612.
How does this compare to the implementation/API done for WinUI 3? Are the same syntax forms accepted there. It'd be good to maintain compatibility and be consistent between WPF and WinUI 3.
I would hope whatever tooling improvements get fixed for Visual Studio to support this will also be done for WinUI 3, as hot reload is not happy and breaks with the new syntax in WinUI 3; so no one uses it, to my knowledge.
@michael-hawker WPF traditionally supports both comma and space separated values in similar collections, which we retained, while I suspect WinUI requires commas. No changes in VS required as the property was made writeable.
Marking this API as ready for review, if there are any objections with the API structure please comment on the following thread.
Why are the converters internal? I don't think we have any non-public type converters yet.
Why are the converters internal? I don't think we have any non-public type converters yet.
@miloush Some of the TypeConverter be internal, such as
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/EventConverter.cs#L10
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Markup/DateTimeConverter2.cs#L20
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/Replacements/DateTimeConverter2.cs#L16
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Xaml/Replacements/TypeListConverter.cs#L15
But more of the TypeConverter be public, such as:
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/Input/ModifierKeysConverter.cs#L14
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/RequestCachePolicyConverter.cs#L16
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/FontFamilyConverter.cs#L16
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/ImageSourceConverter.cs#L19
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/CornerRadiusConverter.cs#L16
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/VirtualizationCacheLengthConverter.cs#L21
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Input/Command/CommandConverter.cs#L24
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/GridLengthConverter.cs#L27
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/IListConverters.cs#L24
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/WindowsBase/System/Windows/ExpressionConverter.cs#L20
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/InputScopeNameConverter.cs#L23
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Markup/SetterTriggerConditionValueConverter.cs#L20
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/System.Xaml/System/Windows/Markup/NameReferenceConverter.cs#L13
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/CursorConverter.cs#L18
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Input/InputScopeConverter.cs#L23
https://github.com/dotnet/wpf/blob/a354bec56e865c2edd1b2af435b5a336da71247b/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media3D/Generated/Vector3DConverter.cs#L29
Can Row/ColumnDefinitionCollection be constructed/used by other elements? If so, then the converter should be public, right?
@miloush There are non-public type converters as pointed out by @lindexi.
@michael-hawker Row/ColumnDefinitionCollection cannot be constructed/used by other elements.
Right now, Row/ColumnDefinitionCollection is not constructable from code behind that's why we have kept their type converters as internal for now.
Yes my bad, I missed the few internal ones, please disregard my comment.
- Looks good as proposed
namespace System.Windows.Controls
{
public partial class Grid
{
+ [TypeConverter(typeof(ColumnDefinitionCollectionConverter))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ColumnDefinitionCollection ColumnDefinitions
{
get { ... }
+ set { ... }
}
+ [TypeConverter(typeof(RowDefinitionCollectionConverter))]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public RowDefinitionCollection RowDefinitions
{
get { ... }
+ set { ... }
}
}
}