Thursday, June 22, 2023

Simple Grid Row and Column Definitions

I was playing with MAUI recently and one thing I really like is the shorthand for defining a grid's row and column definitions. For example, in WPF you see stuff like this.

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="10"/>
            <RowDefinition Height="10"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="400"/>
        </Grid.ColumnDefinitions>
    </Grid>

Which is a lot of XAML containing very little information. In MAUI you can do the same thing with something more like this

    <Grid RowDefinitions="10,10,*,auto" ColumnDefinitions="*,2*,400">
    </Grid>

which contains the same information in a more compressed form. I thought I could subclass Grid and redefine the RowDefinitions and ColumnDefinitions properties to be strings. I created a class called SimpleGrid that does exactly that.

Start a new Visual Studio .Net Framework project using C# and call it SimpleGridDemo.

Add a class called SimpleGrid. It inherits from Grid and overrides the RowDefinitions and ColumnDefinitions dependency properties. When these properties change, it regenerates the row or column definition collections in the base Grid class. It uses the GridLengthConverter class to parse the string grid lengths.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
 
namespace SimpleGrid
{
    internal class SimpleGrid : Grid
    {
        private List<GridLength> RowList = new List<GridLength>();
        private List<GridLength> ColumnList  = new List<GridLength>();
 
        public static readonly DependencyProperty RowDefinitionsProperty = DependencyProperty.Register(nameof(RowDefinitions), typeof(string), typeof(SimpleGrid), new PropertyMetadata("", new PropertyChangedCallback(OnRowsChanged)));
        public new String RowDefinitions
        {
            get { return (String)GetValue(RowDefinitionsProperty); }
            set { SetValue(RowDefinitionsProperty, value); }
        }
 
        private static void OnRowsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SimpleGrid grid = (SimpleGrid)d;
            grid.RowsChanged(e.NewValue.ToString(), e.OldValue.ToString());
        }
 
        private void RowsChanged(String NewRows, string OldRows)
        {
            if (ParseDefinitions(NewRows, out RowList))
            {
                while (base.RowDefinitions.Count() > 0) { base.RowDefinitions.RemoveAt(0); }
                foreach (GridLength gridLength in RowList)
                    base.RowDefinitions.Add(new RowDefinition() { Height = gridLength });
            }
        }
 
        public static readonly DependencyProperty ColumnDefinitionsProperty = DependencyProperty.Register(nameof(ColumnDefinitions), typeof(string), typeof(SimpleGrid), new PropertyMetadata("", new PropertyChangedCallback(OnColumnsChanged)));
        public new String ColumnDefinitions
        {
            get { return (String)GetValue(ColumnDefinitionsProperty); }
            set { SetValue(ColumnDefinitionsProperty, value); }
        }
 
        private static void OnColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SimpleGrid grid = (SimpleGrid)d;
            grid.ColumnsChanged(e.NewValue.ToString(), e.OldValue.ToString());
        }
 
        private void ColumnsChanged(String NewColumns, string OldColumns)
        {
            if (ParseDefinitions(NewColumns, out ColumnList))
            {
                while (base.ColumnDefinitions.Count() > 0) { base.ColumnDefinitions.RemoveAt(0); }
                foreach (GridLength gridLength in ColumnList)
                    base.ColumnDefinitions.Add(new ColumnDefinition() { Width = gridLength });
            }
        }
 
        private bool ParseDefinitions(String Definition, out List<GridLength> gridLengths)
        {
            List<String> parts = Definition.Split(',').ToList();
            GridLength gridLength;
 
            gridLengths = new List<GridLength>();
            foreach (String part in parts)
            {
                if (!ParseDefinition(part.Trim(), out gridLength)) return false;
                gridLengths.Add(gridLength);
            }
            return true;
        }
 
        private bool ParseDefinition(String part, out GridLength gridLength)
        {
            GridLengthConverter gridLengthConverter = new GridLengthConverter();
            gridLength = new GridLength();
            try
            {
                gridLength = (GridLength)gridLengthConverter.ConvertFromString(part);
                return true;
            }
            catch { return false; }
        }
    }
}

Change the MainWindow.xaml and MainWindow.xaml.cs classes to look like this

<Window x:Class="SimpleGridDemo.MainWindow"
        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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SimpleGrid"
        mc:Ignorable="d"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        Title="MainWindow" Height="450" Width="800">
    <local:SimpleGrid RowDefinitions="10,10,*,auto" ColumnDefinitions="*,2*,400" MouseLeftButtonDown="Grid_Click">
        <TextBlock Grid.Row="0" Grid.Column="0" Background="AliceBlue"/>
        <TextBlock Grid.Row="0" Grid.Column="1" Background="Beige"/>
        <TextBlock Grid.Row="0" Grid.Column="2" Background="CadetBlue"/>
        <TextBlock Grid.Row="1" Grid.Column="0" Background="DarkBlue"/>
        <TextBlock Grid.Row="1" Grid.Column="1" Background="Firebrick"/>
        <TextBlock Grid.Row="1" Grid.Column="2" Background="Gainsboro"/>
        <TextBlock Grid.Row="2" Grid.Column="0" Background="Honeydew"/>
        <TextBlock Grid.Row="2" Grid.Column="1" Background="IndianRed"/>
        <TextBlock Grid.Row="2" Grid.Column="2" Background="Khaki"/>
        <TextBlock Grid.Row="3" Grid.Column="0" Background="Lavender"/>
        <TextBlock Grid.Row="3" Grid.Column="1" Background="Magenta"/>
        <TextBlock Grid.Row="3" Grid.Column="2" Background="NavajoWhite"/>
    </local:SimpleGrid>
</Window>

-----------------------------------------------------------------------------------------------------------

using System.Windows;
 
namespace SimpleGridDemo
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
 
        private void Grid_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            SimpleGrid.SimpleGrid grid = (SimpleGrid.SimpleGrid)sender;
            grid.RowDefinitions = "50,*,*,50";
            grid.ColumnDefinitions = "100,*,100";
        }
    }
}

When you first run the program you see the rows and columns have been created as expected. When you click anywhere in the grid, the row and column definitions are changed but the content is preserved.


<Click>