📜 ⬆️ ⬇️

XAML Developer Chips: Dynamic Grid

This article will look at several useful enhancements to the Grid control.
image

The classic Grid usage scenario assumes 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> 

It has several disadvantages:
1. The fall of the brevity of the code with the complexity of the interface
2. It happens when temporarily changing the type of control from a Grid to a StackPanel , for example, you need to delete or comment on the declaration blocks of columns and columns, which is not always convenient
3. Such a Grid is quite static and modifying its columns with columns while the application is running is not too handy and beautiful in the context of the MVVM pattern.

However, there is a very ingenious way to eliminate these shortcomings. Take a look at the following extension Rack (rusk. "Rack")
')
 <Grid Rack.Rows="* 20\Auto * 2* */100 * *" Rack.Columns="* 50\*/100 *"> <TextBlock Rack.Set="R1 C1 RS1 CS2"> <!--...--> </Grid> 

1. The code is concise
2. When changing the type of control, you do not need to comment or delete anything.
3. A high degree of dynamic interface is available.

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

At first, this syntax looks strange, but in fact it is no more difficult than, say, the declaration of vector geometry for Path . In the line [ Rack.Rows = "* 20 \ Auto * 2 * * / 100 * *" ], the columns [columns] declare separated by commas or spaces, and the optional parameters "20 \" and "/ 100" set the minimum and maximum sizes respectively. In turn, [ Rack.Set = "R1 C1 RS1 CS2" ] means assigning values ​​to the properties Grid.Row , Grid.Column , Grid.RowSpan , Grid.ColumnSpan , and it is not necessary to specify all values, that is, the entry [ Rack.Set = "R1 C1" ] is also true.

The extension through nested properties ( atteched properties ) is implemented and included in the Aero Framework library [ backup link ]. The source code is open, so if you do not like, for example, the proposed syntax, then you can easily modify it at your discretion. If you download the library and run the HelloAero project, then see for yourself how dynamic a regular Grid can become using this method of declaration. Just in case I will give 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
Application
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 need to be assembled slightly differently, and creating a new page in many ways with duplicate code does not make sense.

Source: https://habr.com/ru/post/276077/


All Articles