Фишки XAML-разработчика: динамический Grid
В статье рассмотрим несколько полезных усовершенствований для контрола Grid.
Классический сценарий использования Grid предполагает следующий синтаксис
У него имеется ряд недостатков:
1. Падение лаконичности кода при усложнении интерфейса
2. Случается, при временной cмене типа контрола с Grid на StackPanel, например, необходимо удалять либо комментировать блоки декларации колонок и столбцов, что не всегда удобно
3. Такой Grid достаточно статичен и видоизменять его колонки со столбцами во время работы приложения не слишком сподручно и красиво в контексте паттерна MVVM
Однако существует весьма остроумный способ устранения этих недостатков. Взгляните на следующее расширение Rack (руск. «Стеллаж»)
1. Код лаконичен
2. При замене типа контрола ничего не нужно комментировать или удалять
3. Доступна высокая степень динамичности интерфейса
Поначалу такой синтаксис выглядит непривычно, но на деле он не сложнее, чем, скажем, объявление векторной геометрии для Path. В строке [Rack.Rows=»* 20\Auto * 2* */100 * *»] через запятую либо пробел происходит декларация колонок [столбцов], а опциональные параметры »20\» и »/100» задают минимальные и максимальные размеры соответственно. В свою очередь [Rack.Set=«R1 C1 RS1 CS2»] означает присваивание значений свойствам Grid.Row, Grid.Column, Grid.RowSpan, Grid.ColumnSpan, причём все значения указывать не обязательно, то есть запись [Rack.Set=«R1 C1»] также верна.
Реализовано расширение через вложенные свойства (atteched properties) и включено в библиотеку Aero Framework. Исходный код открыт, поэтому, если вам не нравится, к примеру, предложенный синтаксис, то вы запросто можете видоизменить его по своему усмотрению. Если вы скачаете библиотеку и запустите проект HelloAero, то воочию убедитесь, каким динамичным может стать обычный Grid с применением такого способа декларации. На всякий случай приведу пару скриншотов и исходный код ниже.
Спасибо за Ваше внимание!
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();
}
}
}
}