XAML inline RowDefinitions and ColumnDefinitions

By Fons Sonnemans, posted on 01-Dec-2021
3268 Views

In the XAML of WinUI3 apps you can use the inline syntax for creating Rows and Columns inside a Grid. This is a feature which Avalonia had for a long time. It is more compact and works fine as long as you don't need a Max or Min Width/Height.

The following screenshot shows you a Grid with 2 Rows and Columns. Both in equal size. Using 4 Rectangle elements I created the Microsoft logo inside this Grid.

WinUI XAML with normal Row and Column Definitions

The following screenshot shows you the same Grid but now with inline RowDefinitions and ColumnDefinitions. It saves you a few lines of XAML.

WinUI XAML with inline Row and Column Definitions

UWP Support

This inline syntax is only available in WinUI3, not in WPF or UWP. Luckely we can create a similar solution using Attached Properties. So, I created for my UWP projects a helper class called Grid with the two attached Properties: RowDefinitions and ColumnDefinitions.

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Markup;

namespace ReflectionIT.Universal.Helpers {

    /// <summary>
    /// <Grid helpers:Grid.RowDefinitions="1*, 50, Auto,2*"
    ///       helpers:Grid.ColumnDefinitions="1.5*,50,Auto,2.5*" ...
    /// </summary>
    public class Grid {

        #region ColumnDefinitions Attached Property

        /// <summary> 
        /// Identifies the ColumnDefinitions attachted property. This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty ColumnDefinitionsProperty =
            DependencyProperty.RegisterAttached("ColumnDefinitions",
                                                typeof(string),
                                                typeof(Grid),
                                                new PropertyMetadata(default(string), OnColumnDefinitionsChanged));

        /// <summary>
        /// ColumnDefinitions changed handler. 
        /// </summary>
        /// <param name="d">Grid that changed its ColumnDefinitions attached property.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs with the new and old value.</param> 
        private static void OnColumnDefinitionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            if (d is Windows.UI.Xaml.Controls.Grid source) {
                if (source.ColumnDefinitions?.Count == 0) {
                    var value = (string)e.NewValue;
                    var a = value.Split(',');
                    foreach (var text in a) {
                        source.ColumnDefinitions.Add(new Windows.UI.Xaml.Controls.ColumnDefinition {
                            Width = CreateGridLength(text),
                        });
                    }
                }
            }
        }

        /// <summary>
        /// Gets the value of the ColumnDefinitions attached property from the specified Windows.UI.Xaml.Controls.Grid.
        /// </summary>
        public static string GetColumnDefinitions(DependencyObject obj) {
            return (string)obj.GetValue(ColumnDefinitionsProperty);
        }


        /// <summary>
        /// Sets the value of the ColumnDefinitions attached property to the specified Windows.UI.Xaml.Controls.Grid.
        /// </summary>
        /// <param name="obj">The object on which to set the ColumnDefinitions attached property.</param>
        /// <param name="value">The property value to set.</param>
        public static void SetColumnDefinitions(DependencyObject obj, string value) {
            obj.SetValue(ColumnDefinitionsProperty, value);
        }

        #endregion ColumnDefinitions Attached Property

        #region RowDefinitions Attached Property

        /// <summary> 
        /// Identifies the RowDefinitions attachted property. This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty RowDefinitionsProperty =
            DependencyProperty.RegisterAttached("RowDefinitions",
                                                typeof(string),
                                                typeof(Grid),
                                                new PropertyMetadata(default(string), OnRowDefinitionsChanged));

        /// <summary>
        /// RowDefinitions changed handler. 
        /// </summary>
        /// <param name="d">Grid that changed its RowDefinitions attached property.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs with the new and old value.</param> 
        private static void OnRowDefinitionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            if (d is Windows.UI.Xaml.Controls.Grid source) {
                if (source.RowDefinitions?.Count == 0) {
                    var value = (string)e.NewValue;
                    var a = value.Split(',');
                    foreach (var item in a) {
                        source.RowDefinitions.Add(new Windows.UI.Xaml.Controls.RowDefinition {
                            Height = CreateGridLength(item),
                        });
                    }
                }
            }
        }

        /// <summary>
        /// Gets the value of the RowDefinitions attached property from the specified Grid .
        /// </summary>
        public static string GetRowDefinitions(DependencyObject obj) {
            return (string)obj.GetValue(RowDefinitionsProperty);
        }

        /// <summary>
        /// Sets the value of the RowDefinitions attached property to the specified Grid .
        /// </summary>
        /// <param name="obj">The object on which to set the RowDefinitions attached property.</param>
        /// <param name="value">The property value to set.</param>
        public static void SetRowDefinitions(DependencyObject obj, string value) {
            obj.SetValue(RowDefinitionsProperty, value);
        }

        #endregion RowDefinitions Attached Property

        private static GridLength CreateGridLength(string text) {
            text = text.Trim();
            return text.Length==0
                ? throw new XamlParseException("XAML parsing failed: Empty GridLength is not allowed")
                : text.EndsWith('*')
                ? new GridLength(text.Length > 1 ? double.Parse(text.Substring(0, text.Length - 1)) : 1, GridUnitType.Star)
                : text.Equals("auto", StringComparison.OrdinalIgnoreCase)
                    ? new GridLength(0D, GridUnitType.Auto)
                    : new GridLength(double.Parse(text), GridUnitType.Pixel);
        }

    }
}

You can use these properties by first registering the xml namespace inside you XAML. In this case I named it helpers. Inside the Grid element the helpers:Grid.ColumnDefinitions and helpers:Grid.RowDefinitions attributes get the "1*,1*" value. You can off course not only use the Star sizes. Pixel and Auto are also supported. For example the value "40,Auto,*". The first one is Pixel size 40, the second one is Auto and the last one is 1 Star.

<Page
    x:Class="App44.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:helpers="using:ReflectionIT.Universal.Helpers"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">
    <Grid
        Margin="8" ColumnSpacing="8" RowSpacing="8"
        helpers:Grid.ColumnDefinitions="1*,1*"
        helpers:Grid.RowDefinitions="1*,1*">
        <Rectangle Fill="#F25022" />
        <Rectangle Grid.Column="1" Fill="#7FBA00" />
        <Rectangle Grid.Row="1" Fill="#00A4EF" />
        <Rectangle Grid.Row="1" Grid.Column="1" Fill="#FFB900" />
    </Grid>
</Page>

 

WPF Support

The same solution can also be used in WPF. The Grid class is similar as the UWP version, it differs only for some different namespaces.

using System;
using System.Windows;
using System.Xaml;

namespace ReflectionIT.Wpf.Helpers {

    /// <summary>
    /// <Grid helpers:Grid.RowDefinitions="1*, 50, Auto,2*"
    ///       helpers:Grid.ColumnDefinitions="1.5*,50,Auto,2.5*" ...
    /// </summary>
    public class Grid {

        #region ColumnDefinitions Attached Property

        /// <summary> 
        /// Identifies the ColumnDefinitions attachted property. This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty ColumnDefinitionsProperty =
            DependencyProperty.RegisterAttached("ColumnDefinitions",
                                                typeof(string),
                                                typeof(Grid),
                                                new PropertyMetadata(default(string), OnColumnDefinitionsChanged));

        /// <summary>
        /// ColumnDefinitions changed handler. 
        /// </summary>
        /// <param name="d">Grid that changed its ColumnDefinitions attached property.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs with the new and old value.</param> 
        private static void OnColumnDefinitionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            if (d is System.Windows.Controls.Grid source) {
                if (source.ColumnDefinitions?.Count == 0) {
                    var value = (string)e.NewValue;
                    var a = value.Split(',');
                    foreach (var text in a) {
                        source.ColumnDefinitions.Add(new System.Windows.Controls.ColumnDefinition {
                            Width = CreateGridLength(text),
                        });
                    }
                }
            }
        }

        /// <summary>
        /// Gets the value of the ColumnDefinitions attached property from the specified Windows.UI.Xaml.Controls.Grid.
        /// </summary>
        public static string GetColumnDefinitions(DependencyObject obj) {
            return (string)obj.GetValue(ColumnDefinitionsProperty);
        }


        /// <summary>
        /// Sets the value of the ColumnDefinitions attached property to the specified Windows.UI.Xaml.Controls.Grid.
        /// </summary>
        /// <param name="obj">The object on which to set the ColumnDefinitions attached property.</param>
        /// <param name="value">The property value to set.</param>
        public static void SetColumnDefinitions(DependencyObject obj, string value) {
            obj.SetValue(ColumnDefinitionsProperty, value);
        }

        #endregion ColumnDefinitions Attached Property

        #region RowDefinitions Attached Property

        /// <summary> 
        /// Identifies the RowDefinitions attachted property. This enables animation, styling, binding, etc...
        /// </summary>
        public static readonly DependencyProperty RowDefinitionsProperty =
            DependencyProperty.RegisterAttached("RowDefinitions",
                                                typeof(string),
                                                typeof(Grid),
                                                new PropertyMetadata(default(string), OnRowDefinitionsChanged));

        /// <summary>
        /// RowDefinitions changed handler. 
        /// </summary>
        /// <param name="d">Grid that changed its RowDefinitions attached property.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs with the new and old value.</param> 
        private static void OnRowDefinitionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            if (d is System.Windows.Controls.Grid source) {
                if (source.RowDefinitions?.Count == 0) {
                    var value = (string)e.NewValue;
                    var a = value.Split(',');
                    foreach (var item in a) {
                        source.RowDefinitions.Add(new System.Windows.Controls.RowDefinition {
                            Height = CreateGridLength(item),
                        });
                    }
                }
            }
        }

        /// <summary>
        /// Gets the value of the RowDefinitions attached property from the specified Grid .
        /// </summary>
        public static string GetRowDefinitions(DependencyObject obj) {
            return (string)obj.GetValue(RowDefinitionsProperty);
        }

        /// <summary>
        /// Sets the value of the RowDefinitions attached property to the specified Grid .
        /// </summary>
        /// <param name="obj">The object on which to set the RowDefinitions attached property.</param>
        /// <param name="value">The property value to set.</param>
        public static void SetRowDefinitions(DependencyObject obj, string value) {
            obj.SetValue(RowDefinitionsProperty, value);
        }

        #endregion RowDefinitions Attached Property

        private static GridLength CreateGridLength(string text) {
            text = text.Trim();
            return text.Length == 0
                ? throw new XamlParseException("XAML parsing failed: Empty GridLength is not allowed")
                : text.EndsWith('*')
                ? new GridLength(text.Length > 1 ? double.Parse(text.Substring(0, text.Length - 1)) : 1, GridUnitType.Star)
                : text.Equals("auto", StringComparison.OrdinalIgnoreCase)
                    ? new GridLength(0D, GridUnitType.Auto)
                    : new GridLength(double.Parse(text), GridUnitType.Pixel);
        }

    }
}

The usage is the same as in UWP. I had to add Margin to the Rectangle elements due to the lack of RowSpacing and ColumnSpacing on the Grid. Let's hope that will be added to WPF soon. 

WPF XAML with normal Row and Column Definitions

Designer support

The WPF and UWP Designers support the use of these Attached Properties. Let's also hope that a Designer will be added to WinUI3 soon. Untill then we can use XAML Hot Reload and XAML Live Preview as a poor man's alternative for a Designer. Unfortnatly it doesn't support the inline syntax yet. Something which is reported and will hopefully be fixed soon

WPF XAML with normal Row and Column Definitions

All postings/content on this blog are provided "AS IS" with no warranties, and confer no rights. All entries in this blog are my opinion and don't necessarily reflect the opinion of my employer or sponsors. The content on this site is licensed under a Creative Commons Attribution By license.

Leave a comment

Blog comments

0 responses