Blue Flower

In this article we consider some useful improvements to control Grid.
image

The classic use case Grid involves the following syntax

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition MinHeight="20" Height="Auto"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="2*"/>
        <RowDefinition Height="*" MaxHeight="100"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition MinWidth="100" Width="*" MaxWidth="300"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    
    <TextBlock
    	Grid.Row="1"
    	Grid.Column="1"
    	Grid.RowSpan="1"
    	Grid.ColumnSpan="2">
    	
    <!--...-->
</Grid>

There are range of lacks:
1. Falling code brevity at complication interface
2. It happens when a temporary change of control type Grid to StackPanel, for instance, you must delete or comment blocks declaration rows and columns, that is not always convenient
3. The same Grid rather static and modify its columns or rows during the application work is not too handy and nice pattern in the context of MVVM

However, there is a very clever way to address these shortcomings. Take a look at the following extension Rack

<Grid Rack.Rows="* 20\Auto * 2* */100 * *" Rack.Columns="* 50\*/100 *">
    <TextBlock Rack.Set="R1 C1 RS1 CS2">
    <!--...-->
</Grid>

1. Сoncise code
2. If you change the type of control do not need to comment or delete anythink
3. Available high dynamic interface

<Grid 
    Rack.Rows="{Binding Property1, Converter={StaticResource RowsConverter}}" 
    Rack.Columns="{Binding Property1, Converter={StaticResource ColumnsConverter}}" >
    
    <TextBlock Rack.Set="{Binding Property1, Converter={StaticResource TextPositionConverter}}">
    <!--...-->
</Grid>

Initially, the syntax looks strange, but in fact it is no more difficult than, say, the announcement vector geometry for Path. In the line [Rack.Rows="* 20\Auto * 2* */100 * *"] separated by a comma or space occurs Declaration of columns and optional parameters «20\» and «/100» set the minimum and maximum sizes respectively. In its turn [Rack.Set=«R1 C1 RS1 CS2»] means assigning values to properties Grid.Row, Grid.Column, Grid.RowSpan, Grid.ColumnSpan, moreover, all values need not be specified, that is, record [Rack.Set = «R1 C1»] is also true.

Implemented through the expansion of (atteched properties) and included into library Aero Framework [reserve link]. The source code is open, so if you do not like, for example, proposed syntax, you can easily modify it at their discretion. If you download and run the project library HelloAero, then see for themselves how dynamic could become a regular Grid with the use of such a method declaration. In any case, I bring a couple of screenshots and the source code below.

Thank you for your attention!

Screenshots




Source Code
using System;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace Aero.Markup
{
    public static class Rack
    {
        #region Declarations

        public static readonly DependencyProperty ShowLinesProperty = DependencyProperty.RegisterAttached(
            "ShowLines", typeof (bool), typeof (Rack), new PropertyMetadata(default(bool), (o, args) =>
            {
                var grid = o as Grid;
                if (grid == null) return;
                grid.ShowGridLines = Equals(args.NewValue, true);
            }));

        public static void SetShowLines(DependencyObject element, bool value)
        {
            element.SetValue(ShowLinesProperty, value);
        }

        public static bool GetShowLines(DependencyObject element)
        {
            return (bool) element.GetValue(ShowLinesProperty);
        }

        public static readonly DependencyProperty RowsProperty = DependencyProperty.RegisterAttached(
            "Rows", typeof (string), typeof (Rack), new PropertyMetadata("", OnRowsPropertyChanged));

        public static readonly DependencyProperty ColumnsProperty = DependencyProperty.RegisterAttached(
            "Columns", typeof (string), typeof (Rack), new PropertyMetadata("", OnColumnsPropertyChanged));

        public static string GetRows(DependencyObject d)
        {
            return (string) d.GetValue(RowsProperty);
        }

        public static void SetRows(DependencyObject d, string value)
        {
            d.SetValue(RowsProperty, value);
        }

        public static string GetColumns(DependencyObject d)
        {
            return (string) d.GetValue(ColumnsProperty);
        }

        public static void SetColumns(DependencyObject d, string value)
        {
            d.SetValue(ColumnsProperty, value);
        }

        public static readonly DependencyProperty SetProperty = DependencyProperty.RegisterAttached(
            "Set", typeof (string), typeof (Rack), new PropertyMetadata("", OnSetChangedCallback));

        public static void SetSet(DependencyObject element, string value)
        {
            element.SetValue(SetProperty, value);
        }

        public static string GetSet(DependencyObject element)
        {
            return (string) element.GetValue(SetProperty);
        }

        #endregion

        private static void OnRowsPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var grid = o as Grid;
            if (grid == null) return;

            grid.RowDefinitions.Clear();
            var patterns = (e.NewValue as string ?? "").Split(new[] {' ', ','}, StringSplitOptions.RemoveEmptyEntries);
            foreach (var pattern in patterns)
            {
                var indexMin = pattern.IndexOf(@"\", StringComparison.Ordinal);
                var indexMax = pattern.IndexOf(@"/", StringComparison.Ordinal);
                var hasMin = indexMin >= 0;
                var hasMax = indexMax >= 0;
                var valueMin = hasMin ? pattern.Substring(0, indexMin) : "";
                var valueMax = hasMax ? pattern.Substring(indexMax + 1, pattern.Length - indexMax - 1) : "";
                var start = hasMin ? indexMin + 1 : 0;
                var finish = hasMax ? indexMax : pattern.Length;
                var value = pattern.Substring(start, finish - start);
                var definition = new RowDefinition {Height = value.ToGridLength()};
                if (valueMin != "") definition.MinHeight = double.Parse(valueMin);
                if (valueMax != "") definition.MaxHeight = double.Parse(valueMax);
                grid.RowDefinitions.Add(definition);
            }
        }

        private static void OnColumnsPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var grid = o as Grid;
            if (grid == null) return;

            grid.ColumnDefinitions.Clear();
            var patterns = (e.NewValue as string ?? "").Split(new[] {' ', ','}, StringSplitOptions.RemoveEmptyEntries);
            foreach (var pattern in patterns)
            {
                var indexMin = pattern.IndexOf(@"\", StringComparison.Ordinal);
                var indexMax = pattern.IndexOf(@"/", StringComparison.Ordinal);
                var hasMin = indexMin >= 0;
                var hasMax = indexMax >= 0;
                var valueMin = hasMin ? pattern.Substring(0, indexMin) : "";
                var valueMax = hasMax ? pattern.Substring(indexMax + 1, pattern.Length - indexMax - 1) : "";
                var start = hasMin ? indexMin + 1 : 0;
                var finish = hasMax ? indexMax : pattern.Length;
                var value = pattern.Substring(start, finish - start);
                var definition = new ColumnDefinition {Width = value.ToGridLength()};
                if (valueMin != "") definition.MinWidth = double.Parse(valueMin);
                if (valueMax != "") definition.MaxWidth = double.Parse(valueMax);
                grid.ColumnDefinitions.Add(definition);
            }
        }

        private static void OnSetChangedCallback(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            var element = o as FrameworkElement;
            if (element == null) return;
            var patterns = (e.NewValue as string ?? "").Split(new[] {' ', ','}, StringSplitOptions.RemoveEmptyEntries);
            var columnPattern = patterns.FirstOrDefault(p => p.StartsWith("C") && !p.StartsWith("CS")).With(p => p.Replace("C", ""));
            var rowPattern = patterns.FirstOrDefault(p => p.StartsWith("R") && !p.StartsWith("RS")).With(p => p.Replace("R", ""));
            var columnSpanPattern = patterns.FirstOrDefault(p => p.StartsWith("CS")).With(p => p.Replace("CS", ""));
            var rowSpanPattern = patterns.FirstOrDefault(p => p.StartsWith("RS")).With(p => p.Replace("RS", ""));
            int column, row, columnSpan, rowSpan;
            if (int.TryParse(columnSpanPattern, out columnSpan)) Grid.SetColumnSpan(element, columnSpan);
            if (int.TryParse(rowSpanPattern, out rowSpan)) Grid.SetRowSpan(element, rowSpan);
            if (int.TryParse(columnPattern, out column)) Grid.SetColumn(element, column);
            if (int.TryParse(rowPattern, out row)) Grid.SetRow(element, row);
        }

        private static GridLength ToGridLength(this string length)
        {
            try
            {
                length = length.Trim();
                if (length.ToLowerInvariant().Equals("auto")) return new GridLength(0, GridUnitType.Auto);
                if (!length.Contains("*")) return new GridLength(double.Parse(length), GridUnitType.Pixel);
                length = length.Replace("*", "");
                if (string.IsNullOrEmpty(length)) length = "1";
                return new GridLength(double.Parse(length), GridUnitType.Star);
            }
            catch (Exception exception)
            {
                Debug.WriteLine(exception.Message);
                return new GridLength();
            }
        }
    }
}


Update
Using
Dynamic Grid can be very useful in the development of complex interfaces, as well as, for example, in cases where the application supports portrait and landscape orientation, depending on which visual elements you need to assemble a little differently, but to create a new page in many ways with duplicates code It does not make sense.