В статье рассмотрим несколько полезных усовершенствований для контрола Grid.
Классический сценарий использования Grid предполагает следующий синтаксис
<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>
У него имеется ряд недостатков:
1. Падение лаконичности кода при усложнении интерфейса
2. Случается, при временной cмене типа контрола с Grid на StackPanel, например, необходимо удалять либо комментировать блоки декларации колонок и столбцов, что не всегда удобно
3. Такой Grid достаточно статичен и видоизменять его колонки со столбцами во время работы приложения не слишком сподручно и красиво в контексте паттерна MVVM
Однако существует весьма остроумный способ устранения этих недостатков. Взгляните на следующее расширение Rack (руск. «Стеллаж»)
<Grid Rack.Rows="* 20Auto * 2* */100 * *" Rack.Columns="* 50*/100 *">
<TextBlock Rack.Set="R1 C1 RS1 CS2">
<!--...-->
</Grid>
1. Код лаконичен
2. При замене типа контрола ничего не нужно комментировать или удалять
3. Доступна высокая степень динамичности интерфейса
<Grid
Rack.Rows="{Binding Property1, Converter={StaticResource RowsConverter}}"
Rack.Columns="{Binding Property1, Converter={StaticResource ColumnsConverter}}" >
<TextBlock Rack.Set="{Binding Property1, Converter={StaticResource TextPositionConverter}}">
<!--...-->
</Grid>
Поначалу такой синтаксис выглядит непривычно, но на деле он не сложнее, чем, скажем, объявление векторной геометрии для Path. В строке [Rack.Rows="* 20Auto * 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();
}
}
}
}
Автор: Makeman