wpf icon indicating copy to clipboard operation
wpf copied to clipboard

[API Proposal]: Simplify Grid Column and Row Definitions

Open himgoyalmicro opened this issue 1 year ago • 15 comments

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

himgoyalmicro avatar Sep 18 '24 09:09 himgoyalmicro

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.

miloush avatar Sep 18 '24 11:09 miloush

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 avatar Sep 18 '24 15:09 h3xds1nz

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

himgoyalmicro avatar Sep 19 '24 10:09 himgoyalmicro

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

himgoyalmicro avatar Jan 29 '25 16:01 himgoyalmicro

It might be helpful to post on the original thread for any additional feedback imho, as I'd expect some people subscribed there.

h3xds1nz avatar Jan 29 '25 21:01 h3xds1nz

@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

Rand-Random avatar Feb 03 '25 05:02 Rand-Random

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 avatar Feb 03 '25 09:02 michael-hawker

@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.

miloush avatar Feb 11 '25 14:02 miloush

Marking this API as ready for review, if there are any objections with the API structure please comment on the following thread.

himgoyalmicro avatar Mar 11 '25 11:03 himgoyalmicro

Why are the converters internal? I don't think we have any non-public type converters yet.

miloush avatar Mar 12 '25 12:03 miloush

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

lindexi avatar Mar 13 '25 00:03 lindexi

Can Row/ColumnDefinitionCollection be constructed/used by other elements? If so, then the converter should be public, right?

michael-hawker avatar Mar 13 '25 01:03 michael-hawker

@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.

himgoyalmicro avatar Mar 13 '25 12:03 himgoyalmicro

Yes my bad, I missed the few internal ones, please disregard my comment.

miloush avatar Mar 13 '25 12:03 miloush

Video

  • 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 { ... }
        }
    }
}

bartonjs avatar Apr 15 '25 18:04 bartonjs