Свойство Tag в WinRT
63WinRT --- Свойство Tag
Группа элементов управления RadioButton позволяет выбрать один из нескольких взаимоисключающих вариантов. С точки зрения программиста часто бывает удобно, чтобы каждый элемент RadioButton в конкретной группе соответствовал элементу перечисления, а значение перечислимого типа идентифицировалось объектом RadioButton. В этом случае все кнопки в группе могут совместно использовать один обработчик события.
Свойство Tag идеально подходит для этой цели. В нем можно сохранить любую информацию, предназначенную для идентификации элемента. Допустим, вы хотите написать программу для экспериментов со свойствами StrokeStartLineCap, StrokeEndLineCap и StrokeLineJoin, определяемыми классом Shape. При рисовании толстых линий эти свойства управляют формой концов и соединений сегментов. Свойствам StrokeStartLineCap и StrokeEndLineCap задаются значения из перечислимого типа PenLineCap, а свойству StrokeLineJoin - значения из перечислимого типа PenLineJoin.
Например, в перечисление PenLineJoin входит значение Bevel. Для представления этого варианта можно определить следующий элемент RadioButton:
<RadioButton Content="Коническая линия"
Tag="Bevel" />
Недостаток такого решения заключается в том, что парсер XAML интерпретирует "Bevel" как строку, поэтому в обработчике события в файле отделенного кода приходится использовать конструкцию switch/case, чтобы различать разные строки, или преобразовывать строки в фактическое значение PenLineJoin.Bevel методом Enum.TryParse.
Свойство Tag лучше определить иначе: выделив его в элемент свойства и явно указав, что ему задается значение типа PenLineJoin:
<RadioButton Content="Коническая линия">
<RadioButton.Tag>
<PenLineJoin>Bevel</PenLineJoin>
</RadioButton.Tag>
</RadioButton>
Конечно, решение получается немного громоздким, однако я использовал его в тестовом проекте. В файле XAML определяются три группы элементов управления RadioButton для трех свойств Shape. Каждая группа содержит три или четыре элемента управления, соответствующих членам перечисления:
<Page ...>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.Resources>
<Style TargetType="RadioButton">
<Setter Property="FontSize" Value="18" />
</Style>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Name="startLineCapPanel"
Grid.Row="0" Grid.Column="0"
Margin="24">
<RadioButton Content="Обрезанное начало прямой"
Checked="OnStartLineCapRadioButtonChecked">
<RadioButton.Tag>
<PenLineCap>Flat</PenLineCap>
</RadioButton.Tag>
</RadioButton>
<RadioButton Content="Округлое начало прямой"
Checked="OnStartLineCapRadioButtonChecked">
<RadioButton.Tag>
<PenLineCap>Round</PenLineCap>
</RadioButton.Tag>
</RadioButton>
<RadioButton Content="Квадратное начало прямой"
Checked="OnStartLineCapRadioButtonChecked">
<RadioButton.Tag>
<PenLineCap>Square</PenLineCap>
</RadioButton.Tag>
</RadioButton>
<RadioButton Content="Треугольное начало прямой"
Checked="OnStartLineCapRadioButtonChecked">
<RadioButton.Tag>
<PenLineCap>Triangle</PenLineCap>
</RadioButton.Tag>
</RadioButton>
</StackPanel>
<StackPanel Name="endLineCapPanel"
Grid.Row="0" Grid.Column="2"
Margin="24">
<RadioButton Content="Обрезанный конец прямой"
Checked="OnEndLineCapRadioButtonChecked">
<RadioButton.Tag>
<PenLineCap>Flat</PenLineCap>
</RadioButton.Tag>
</RadioButton>
<RadioButton Content="Округлый конец прямой"
Checked="OnEndLineCapRadioButtonChecked">
<RadioButton.Tag>
<PenLineCap>Round</PenLineCap>
</RadioButton.Tag>
</RadioButton>
<RadioButton Content="Квадратный конец прямой"
Checked="OnEndLineCapRadioButtonChecked">
<RadioButton.Tag>
<PenLineCap>Square</PenLineCap>
</RadioButton.Tag>
</RadioButton>
<RadioButton Content="Треугольный конец прямой"
Checked="OnEndLineCapRadioButtonChecked">
<RadioButton.Tag>
<PenLineCap>Triangle</PenLineCap>
</RadioButton.Tag>
</RadioButton>
</StackPanel>
<StackPanel Name="lineJoinPanel"
Grid.Row="1" Grid.Column="1"
HorizontalAlignment="Center"
Margin="24">
<RadioButton Content="Конический стык"
Checked="OnLineJoinRadioButtonChecked">
<RadioButton.Tag>
<PenLineJoin>Bevel</PenLineJoin>
</RadioButton.Tag>
</RadioButton>
<RadioButton Content="Прямой стык"
Checked="OnLineJoinRadioButtonChecked">
<RadioButton.Tag>
<PenLineJoin>Miter</PenLineJoin>
</RadioButton.Tag>
</RadioButton>
<RadioButton Content="Округлый стык"
Checked="OnLineJoinRadioButtonChecked">
<RadioButton.Tag>
<PenLineJoin>Round</PenLineJoin>
</RadioButton.Tag>
</RadioButton>
</StackPanel>
<Polyline Name="polyline"
Grid.Row="0"
Grid.Column="1"
Points="0 0, 500 1000, 1000 0"
Stroke="{StaticResource ApplicationForegroundThemeBrush}"
StrokeThickness="100"
Stretch="Fill"
Margin="24" />
</Grid>
</Page>
Каждая из трех групп RadioButton находится в собственной панели StackPanel, а все элементы управления в одной панели совместно используют один обработчик события Checked.
Разметка не устанавливает ни один элемент RadioButton в помеченном состоянии - за это отвечает обработчик события Loaded, определенный в конструкторе в файле отделенного кода. (Почему-то при выполнении инициализации в конструкторе вместо обработчика Loaded элемент RadioButton для способа соединения инициализируется, а два других - нет.)
В конце разметки определяется объект Polyline, ожидающий задания своих свойств StrokeStartLineCap, StrokeEndLineCap и StrokeLineJoin. Это происходит в следующих трех обработчиках событий Checked, которые также определяются в файле отделенного кода:
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
namespace WinRTTestApp
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
Loaded += (sender, args) =>
{
foreach (UIElement child in startLineCapPanel.Children)
(child as RadioButton).IsChecked =
(PenLineCap)(child as RadioButton).Tag == polyline.StrokeStartLineCap;
foreach (UIElement child in endLineCapPanel.Children)
(child as RadioButton).IsChecked =
(PenLineCap)(child as RadioButton).Tag == polyline.StrokeEndLineCap;
foreach (UIElement child in lineJoinPanel.Children)
(child as RadioButton).IsChecked =
(PenLineJoin)(child as RadioButton).Tag == polyline.StrokeLineJoin;
};
}
private void OnStartLineCapRadioButtonChecked(object sender, RoutedEventArgs args)
{
polyline.StrokeStartLineCap = (PenLineCap)(sender as RadioButton).Tag;
}
private void OnEndLineCapRadioButtonChecked(object sender, RoutedEventArgs args)
{
polyline.StrokeEndLineCap = (PenLineCap)(sender as RadioButton).Tag;
}
private void OnLineJoinRadioButtonChecked(object sender, RoutedEventArgs args)
{
polyline.StrokeLineJoin = (PenLineJoin)(sender as RadioButton).Tag;
}
}
}
Обработчик Loaded перебирает элементы управления RadioButton во всех группах, задавая свойству IsChecked значение true, если значение Tag совпадает с соответствующим свойством Polyline. Вся дополнительная проверка RadioButton происходит под контролем пользователя. Обработчику события достаточно задать свойство объекта Polyline на основании свойства Tag установленного переключателя RadioButton. Результат выглядит так:
Хотя разметка однозначно показывает, что свойству Tag задается элемент перечисления PenLineCap или PenLineJoin, парсер XAML фактически задает соответствующее ему целое число. Это целое число легко преобразуется в правильный элемент перечисления, но определенно не является им.
Чтобы устранить большую часть громоздкой разметки, достаточно определить пару простых пользовательских элементов управления. Наличие свойств зависимости у этих элементов управления не обязательно; достаточно обычного свойства .NET для тега, соответствующего конкретному типу.
Ниже показано, как работает эта схема. Следующие классы, производные от RadioButton, определяются специально для представления значений PenLineCap и PenLineJoin:
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
namespace WinRTTestApp
{
public class LineCapRadioButton : RadioButton
{
public PenLineCap LineCapTag { set; get; }
}
public class LineJoinRadioButton : RadioButton
{
public PenLineJoin LineJoinTag { set; get; }
}
}
Приведу маленький фрагмент XAML (группа трех элементов управления RadioButton), демонстрирующий, как обойтись без синтаксиса элементов свойств:
...
<StackPanel Name="startLineCapPanel"
Grid.Row="0" Grid.Column="0"
Margin="24">
<local:LineCapRadioButton Content="Обрезанное начало прямой"
LineCapTag="Flat"
Checked="OnStartLineCapRadioButtonChecked" />
<local:LineCapRadioButton Content="Округлое начало прямой"
LineCapTag="Round"
Checked="OnStartLineCapRadioButtonChecked" />
<local:LineCapRadioButton Content="Квадратное начало прямой"
LineCapTag="Square"
Checked="OnStartLineCapRadioButtonChecked" />
<local:LineCapRadioButton Content="Треугольное начало прямой"
LineCapTag="Triangle"
Checked="OnStartLineCapRadioButtonChecked" />
</StackPanel>
...
Обратите внимание: при вводе этой разметки IntelliSense правильно распознает свойства LineCapTag и LineJoinTag как относящиеся к перечислимому типу и дает возможность ввести один из элементов перечисления. Удобно, ничего не скажешь!
Переход на классы, производные от RadioButton, в основном влияет на файл XAML. Файл отделенного кода остается практически неизменным, если не считать сокращения преобразования типов:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
Loaded += (sender, args) =>
{
foreach (UIElement child in startLineCapPanel.Children)
(child as LineCapRadioButton).IsChecked =
(child as LineCapRadioButton).LineCapTag == polyline.StrokeStartLineCap;
foreach (UIElement child in endLineCapPanel.Children)
(child as LineCapRadioButton).IsChecked =
(child as LineCapRadioButton).LineCapTag == polyline.StrokeEndLineCap;
foreach (UIElement child in lineJoinPanel.Children)
(child as LineJoinRadioButton).IsChecked =
(child as LineJoinRadioButton).LineJoinTag == polyline.StrokeLineJoin;
};
}
private void OnStartLineCapRadioButtonChecked(object sender, RoutedEventArgs args)
{
polyline.StrokeStartLineCap = (sender as LineCapRadioButton).LineCapTag;
}
private void OnEndLineCapRadioButtonChecked(object sender, RoutedEventArgs args)
{
polyline.StrokeEndLineCap = (sender as LineCapRadioButton).LineCapTag;
}
private void OnLineJoinRadioButtonChecked(object sender, RoutedEventArgs args)
{
polyline.StrokeLineJoin = (sender as LineJoinRadioButton).LineJoinTag;
}
}