WPF: пользовательские элементы
Что собой представляют пользовательские элементы в WPF
Хотя пользовательский элемент
можно построить в любом
Чтобы гарантировать наличие
всех необходимых ссылок на сборки
и импорт всенужных пространств
имен, при создании приложения в Visual
Studio в качестве типа проекта следует
выбрать Custom Control Library (WPF) (Библиотека пользовательских
элементов управления (WPF)). Внутри библиотеки
классов можно создавать
Как при разработке любой библиотеки классов, часто стоит помещать как саму библиотеку классов, так и приложение, использующее ее, в одно и то же решение Visual Studio. Это позволит легко модифицировать и отлаживать обе части вместе.
Первый шаг в создании пользовательского элемента управления — это выбор корректного базового класса для наследования. Теперь перечислим некоторые часто применяемые классы для создания пользовательских элементов управления, а на рисунке покажем их расположение в иерархии элементов:
Таблица 1. Базовые классы для создания пользовательских элементов
Имя |
Описание |
FrameworkElement |
Это — самый низкий уровень, с которым обычно приходится иметь дело при создании пользовательского элемента. Обычно такой подход выбирается только тогда, когда нужно нарисовать его содержимое "с нуля" посредством переопределения OnRenderO и использования System. Windows.Media.DrawingContext. Класс FrameworkElement предоставляет лишь самый
базовый набор свойств и элементов, которые не предназначены для взаимодействия с пользователем |
Control |
Этот класс чаще всего служит начальной точкой при построении элемента управления "с нуля". Это — базовый класс для всех взаимодействующих с пользователем графических элементов управления. Класс Control добавляет свойства для установки фона и переднего плана, а также шрифта и выравнивания содержимого. Кроме того, этот класс помещает себя в последовательность обхода по клавише <ТаЬ> (свойством isTabStop) и получает уведомления о двойном щелчке (через события MouseDoubleClick и PreviewMouseDoubleClick). Но что более важно, так это то, что класс Control определяет свойство Template, позволяющее заменять его внешний вид с неограниченной гибкостью |
ContentControl |
Это — базовый класс для элементов управления, которые могут отображаться как единое целое с произвольным содержимым. Содержимое может быть элементом пользовательского объекта, применяемого в сочетании с шаблоном. (Содержимое устанавливается свойством Content, а необязательный шаблон может быть представлен в свойстве ContentTemplate.) Многие элементы управления упаковывают специфический, ограниченный тип содержимого (вроде строки текста в текстовом поле). Поскольку эти элементы управления не поддерживают всех элементов, они не должны определяться как элементы управления с содержимым |
UserControl |
Это элемент управления с содержимым, который может быть сконфигурирован с применением поверхности времени проектирования. Хотя такой пользовательский элемент управления не настолько отличается от обычного элемента управления с содержимым, обычно он используется тогда, когда необходимо быстро повторно применить неизменный блок пользовательского интерфейса в более чем одном окне (вместо создания действительно отдельного элемента управления, который может быть перенесен из одного приложения в другое) |
ItemsControl или Selector |
itemsControl — базовый класс для элементов управления, служащих оболочками для списков элементов, но не поддерживающих выбор позиций, в то время как Selector — более специализированный базовый класс для элементов, поддерживающих выбор. Эти классы нечасто применяются для создания пользовательских элементов управления, поскольку средства шаблонов данных ListBox, ListView и TreeView обеспечивают достаточную гибкость |
Panel |
Базовый класс для элементов управления, обладающих логикой компоновки. Элемент с компоновкой может содержать в себе множество дочерних элементов и размещать их в соответствии с определенной семантикой компоновки. Часто панели включают присоединенные свойства, которые могут быть установлены в дочерние элементы для того, чтобы конфигурировать их расположение |
Decorator |
Это базовый класс для элементов, служащих оболочками для других элементов и обеспечивающих графический эффект или определенное средство. Двумя яркими примерами могут служить Border, который рисует линию вокруг элемента, и Viewbox, масштабирующий свое содержимое динамически с использованием трансформаций. Среди других декораций — классы Chrome, служащие для снабжения знакомыми рамками и фоном часто используемых элементов управления, таких как кнопка |
Специфический класс элемента управления |
Если необходимо усовершенствовать существующий элемент управления, можно наследоваться непосредственно от класса этого элемента. Например, можно создать элемент TextBox со встроенной логикой проверки достоверности (что будет продемонстрировано далее в настоящей главе). Однако прежде чем предпринять такой шаг, подумайте, нельзя ли достичь той же цели с помощью кода обработки событий или отдельного компонента. Оба подхода позволят отделить логику от элемента управления и применять ее в других элементах |
Рис. 1. Базовые классы простых
элементов и элементов
Хотя допускается построить специальный элемент, который не является элементом управления, большинство пользовательских элементов, создаваемых в WPF, будут именно элементами управления. Это значит, что они смогут принимать фокус, а также взаимодействовать с пользовательскими нажатиями клавиш и действиями мыши. По этой причине термины пользовательские элементы и пользовательские элементы управления при разработке WPF-приложений часто являются взаимозаменяемыми.
Хороший способ начать разработку пользовательских элементов управления — попробовать создать самый простой элемент. Мы начнем с создания базового указателя цвета.
Создание указателя цвета достаточно просто. В Интернете доступно несколько примеров такого инструмента, в том числе один в комплекте .NET Framework SDK. Тем не менее, создание собственного инструмента для выбора цвета остается полезным упражнением. Оно не только позволяет продемонстрировать широкое разнообразие важных концепций построения элементов управления, но также предоставляет практичный кусок функциональности.
Для начала можно создать специализированное диалоговое окно для выбора цвета, подобное тому, что включено в Windows Forms. Но если необходим указатель цвета, который можно было бы интегрировать в различные окна, то пользовательский элемент управления — намного лучший вариант. Наиболее простой тип специализированного элемента — пользовательский элемент управления, который позволяет собрать комбинацию элементов подобно тому, как это делается при проектировании окна или страницы. Поскольку указатель цвета должен выглядеть несколько сложнее, чем примитивная группа существующих элементов управления с дополнительной функциональностью, он представляется наилучшим выбором.
Обычный указатель цвета позволяет пользователю выбирать цвет щелчком где-то на поле цветового градиента либо указанием индивидуальных составляющих красного, зеленого и синего цветов. На рисунке показан базовый указатель цвета, который будет создан в этом разделе (в верхней части окна). Он состоит из трех элементов управления Slider для настройки цветовых составляющих, а также элементом Rectangle для предварительного отображения выбранного цвета.
Подход на основе пользовательских элементов управления обладает одним существенным недостатком — он ограничивает возможности настройки внешнего вида указателя цвета с целью адаптации к разным окнам, приложениям и пользователям. К счастью, ненамного сложнее сделать следующий шаг к элементу управления на основе шаблона.
Определение свойств зависимости
Первый шаг в создании указателя цвета — добавление пользовательского элемента управления в проект библиотеки элементов управления. Когда это делается, Visual Studio создает файл разметки XAML и соответствующий специальный класс, чтобы определить в них инициализацию и код обработки событий. Это то же самое, что приходится делать при создании нового окна или страницы. Единственное отличие в том, что контейнером верхнего уровня выступает класс UserControl:
public partial class ColorPicker : System.Windows.Controls.
{ ... }
Далее легче всего начать с проектирования общедоступного интерфейса, который пользовательский элемент управления предоставит внешнему миру. Другими словами, нужно создать свойства, методы и события, которые будут поступать в этот элемент управления, и на которые будет опираться приложение, использующее его для взаимодействия с указателем цвета.
Наиболее фундаментальной деталью является свойство Color. В конце концов, указатель цвета — не что иное, как специализированный инструмент для отображения и выбора цветового значения. Чтобы поддержать такие средства WPF, как привязка данных, стили и анимация, доступные для записи свойства элемента управления почти всегда должны быть свойствами зависимости.
Как известно, первый шаг в создании свойства зависимости — это определение статического поля для него с добавленным словом Property в конце его имени:
public static DependencyProperty ColorProperty;
Свойство Color позволит коду,
использующему этот элемент управления,
программно устанавливать или извлекать
значение цвета. Однако ползунки в указателе
цвета также позволят пользователю
модифицировать по одному аспекту текущего
цвета. Для реализации такого проектного
решения можно применить
public static DependencyProperty RedProperty;
public static DependencyProperty GreenProperty;
public static DependencyProperty BlueProperty;
Хотя свойство Color будет хранить объект System.Windows.Media.Color, свойства Red, Green и Blue будут хранить индивидуальные байтовые значения, представляющие каждый из трех компонентов цвета. (Можно также добавить ползунок и свойство для установки альфа-значения, что позволит создавать частично прозрачные цвета, но в данном примере это не делается.)
Определение статических
полей для свойств — лишь первый
шаг. Также понадобится статический
конструктор элемента управления, который
зарегистрирует свойства, указывая имя
свойства, тип данных и класс элемента
управления, владеющий данным свойством.
Как было показано, это позволит
воспользоваться некоторыми специфическими
средствами свойств (вроде наследования
значений) за счет передачи объекта FrameworkPropertyMetad
В указателе цвета нужно сделать только одно — добавить обратные вызовы, которые будут реагировать на изменение различных свойств. Это объясняется тем, что свойства Red, Green и Blue — на самом деле просто другое представление свойства Color, и при изменении любого из этих трех следует обеспечить синхронизацию последнего.
Ниже приведен код статического конструктора, регистрирующего четыре свойства зависимости для указателя цвета:
static ColorPicker()
{
// Регистрация свойств зависимости
ColorProperty = DependencyProperty.Register("
new FrameworkPropertyMetadata(
RedProperty = DependencyProperty.Register("
new FrameworkPropertyMetadata(new PropertyChangedCallback(
GreenProperty = DependencyProperty.Register("
new FrameworkPropertyMetadata(new PropertyChangedCallback(
BlueProperty = DependencyProperty.Register("
new FrameworkPropertyMetadata(new PropertyChangedCallback(
}
Теперь, определив свойства зависимости, можно добавить стандартные оболочки для свойств, которые облегчают доступ к ним и обеспечивают возможность обращения из XAML-разметки:
public Color Color
{
get { return (Color)GetValue(ColorProperty)
set { SetValue(ColorProperty, value); }
}
public byte Red
{
get { return (byte)GetValue(RedProperty); }
set { SetValue(RedProperty, value); }
}
public byte Green
{
get { return (byte)GetValue(GreenProperty); }
set { SetValue(GreenProperty, value); }
}
public byte Blue
{
get { return (byte)GetValue(BlueProperty); }
set { SetValue(BlueProperty, value); }
}
Вспомните, что оболочки
свойств не должны содержать никакой
логики, поскольку свойства могут
устанавливаться и извлекаться
непосредственно с помощью
Обратные вызовы изменения свойств отвечают за сохранение соответствия свойства Color текущим значениям Red, Green и Blue. Всякий раз, когда изменяется свойство Red, Green или Blue, свойство Color тоже соответствующим образом модифицируется:
private static void OnColorRGBChanged(
DependencyPropertyChangedEvent
{
ColorPicker colorPicker = (ColorPicker)sender;
Color color = colorPicker.Color;
if (e.Property == RedProperty)
color.R = (byte)e.NewValue;
else if (e.Property == GreenProperty)
color.G = (byte)e.NewValue;
else if (e.Property == BlueProperty)
color.B = (byte)e.NewValue;
}
В случае установки свойства Color свойства Red, Green и Blue также обновляются:
private static void OnColorChanged(
DependencyPropertyChangedEvent
{
Color newColor = (Color)e.NewValue;
ColorPicker colorpicker = (ColorPicker)sender;
colorpicker.Red = newColor.R;
colorpicker.Green = newColor.G;
colorpicker.Blue = newColor.B;
}
Хотя на первый взгляд может показаться, что такой код инициирует бесконечную последовательность вызовов, когда каждое свойство будет изменять другое, на самом деле подобного не происходит. Это объясняется тем, что WPF не допускает повторного вхождения при обратных вызовах изменения свойств. Например, при изменении свойства Color инициируется метод OnClolorChanged(). Он модифицирует свойства Red, Green и Blue, генерируя три раза обратный вызов OnColorRGBChanged() (по одному для каждого из свойств). Однако OnColorRGBChanged() не вызовет еще раз OnClolorChanged().
Может случиться так, что для обработки свойств цвета будут применены принудительные обратные вызовы. Однако такой подход нецелесообразен. Принудительные обратные вызовы свойств предназначены для взаимосвязанных свойств, которые могут переопределять или влиять друг на друга. Они не имеют смысла для свойств, представляющих одни и те же данные разными способами. Если вы примените принудительные свойства в данном примере, то станет возможно устанавливать разные значения свойств Red, Green и Blue, тем самым переопределяя цветовую информацию свойства Color. Поведение, которое в действительности нужно, заключается в установке свойств Red, Green и Blue и применении этой информации для постоянного изменения значения свойства Color.
Определение маршрутизируемых событий
Также может понадобиться
добавить маршрутизируемые события, которые
позволяют уведомлять потребителя
элемента управления о том, что что-то
произошло. В примере с указателем
цвета удобно иметь событие, возбуждаемое
при изменении цвета. Хотя это
событие может быть определено как
обычное событие .NET, применение маршрутизируемого
события позволит организовать пузырьковое
распространение и
Как и в случае свойств зависимости, первый шаг в определении маршрутизируемого события — это создание статического свойства для него, со словом Event, добавленным в конец имени:
public static readonly RoutedEvent ColorChangedEvent;
Затем можно зарегистрировать это событие в статическом конструкторе. При этом указывается имя события, стратегия маршрутизации, сигнатура и класс-владелец:
// Регистрация маршрутизируемого события
ColorChangedEvent = EventManager.
typeof(
Вместо того, чтобы разрабатывать новый делегат для сигнатуры события, иногда можно воспользоваться существующими делегатами.
Два полезных делегата, которые
могут пригодиться в этом случае
— это RoutedEventHandler (для
маршрутизируемых событий, не передающих
никакой дополнительной информации) и RoutedPropertyChangedEventHa
Определив и зарегистрировав
событие, понадобится создать
public event RoutedPropertyChangedEventHand
{
add { AddHandler(ColorChangedEvent, value); }
remove { RemoveHandler(
}
Вспомните, что обратный вызов OnColorChanged() инициируется при любой модификации свойства Color — будь то непосредственно или же при изменении компонентов цвета Red, Green и Blue.
Теперь, когда общедоступный
интерфейс пользовательского
В конце концов, в указателе цвета будут работать четыре выражения привязки данных. Три ползунка привязаны к свойствам Red, Green и Blue, разрешая их изменение в диапазоне от 0 до 255 (допустимые значения для байта). Свойство Rectangle.Fill устанавливается в SolidColorBrush, а свойство Color этой кисти привязано к свойству Color пользовательского элемента управления. Ниже приведен полный код разметки:
<UserControl x:Class="MyBaseUserControl.
xmlns="http://schemas.
xmlns:x="http://schemas.
Name="colorPicker">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></
<ColumnDefinition Width="auto"></
</Grid.ColumnDefinitions>
<Slider Name="sliderRed" Minimum="0" Maximum="255"
Value="{Binding ElementName=colorPicker, Path=Red}"></Slider>
<Slider Name="sliderGreen" Minimum="0" Maximum="255" Grid.Row="1"
Value="{Binding ElementName=colorPicker, Path=Green}"></Slider>
<Slider Name="sliderBlue" Minimum="0" Maximum="255" Grid.Row="2"
Value="{Binding ElementName=colorPicker, Path=Blue}"></Slider>
<Rectangle Grid.Column="1" Grid.RowSpan="3" Width="50" Stroke="Black" StrokeThickness="1"
StrokeDashArray="2 1">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding ElementName=colorPicker,Path=
</Rectangle.Fill>
</Rectangle>
</Grid>
</UserControl>
Код разметки для пользовательского элемента управления играет ту же роль, что и шаблон элемента управления для элемента, не имеющего внешнего вида. Если хотите сделать некоторые детали кода разметки конфигурируемыми, можете воспользоваться выражениями привязки, чтобы связать их с другими свойствами элемента управления.
Например, в приведенном коде ширина элемента Rectangle жестко закодирована и составляет 50 единиц. Однако можно заменить его выражением привязки данных, которое будет устанавливать значение ширины прямоугольника по свойству зависимости пользовательского элемента управления. Таким образом, пользователь элемента сможет модифицировать это свойство, выбирая другую ширину. Аналогично можно поступить с цветом рамки и переменной ее толщины. Однако чтобы достичь настоящей гибкости элемента управления, лучше создать элемент, лишенный внешнего вида, и определить разметку в форме шаблона.
Иногда может быть отдано предпочтение выражению привязки для изменения одного из центральных свойств, которые уже определены в элементе управления. Например, класс UserControl использует свое свойство Padding для добавления отступа между внешней гранью и заданным внутренним содержимым. (Эта деталь реализуется через шаблон элемента управления для UserControl.) Однако также потребуется применить свойство Padding для установки отступов вокруг каждого ползунка:
<Slider Name="sliderRed" Minimum="0" Maximum="255"
Margin="{Binding ElementName=colorPicker,Path=
Value="{Binding ElementName=colorPicker,Path=
Аналогично можно получить
настройки рамки для Rectangle из свойств
BorderThickness и BorderBrush класса UserControl. Опять-таки,
это сокращение, которое может
быть оправдано при создании простых
элементов управления, но которое
можно усовершенствовать
Именование пользовательских элементов управления
В приведенном примере элементу UserControl верхнего уровня назначено имя (colorPicker). Это позволяет писать простые выражения привязки данных, которые связывают свойства в классе пользовательского элемента управления. Однако такой прием вызывает очевидный вопрос, а именно: что происходит, когда в окне (или на странице) создается экземпляр пользовательского элемента управления, которому назначается новое имя?
К счастью, никаких ошибок при этом не возникает, поскольку пользовательский элемент управления выполняет инициализацию до того, как это сделает содержащее его окно. Сначала инициализируется пользовательский элемент управления, и подключаются его привязки данных. Затем инициализируется окно, и имя, указанное в разметке окна назначается пользовательскому элементу управления. Выражения привязки данных и обработчики событий в окне теперь могут применять определенное в окне имя для доступа к пользовательскому элементу управления, и все работает так, как ожидалось.
Хотя это решение достаточно
очевидно, можно заметить пару причуд,
если используется код, проверяющий
значение свойства UserControl.Name напрямую.
Например, если проверить свойство
Name в обработчике событий
Ни одна из этих странностей
не является проблемой, но лучший подход
заключается в том, чтобы избегать
именования пользовательского элемента
управления в коде его разметки и
применять свойство Binding.
<Slider Name="sliderRed" Minimum="0" Maximum="255"
Value="{Binding Path=Red, RelativeSource={RelativeSource
FindAncestor,AncestorType={x:
</Slider>
Использование элемента управления
Когда создание элемента управления завершено, использовать его очень легко. Чтобы применить указатель цвета в другом окне, понадобится отобразить сборку и пространство имен .NET на пространство имен XML, как показано ниже:
<Window x:Class="WpfApplication1.
xmlns="http://schemas.
xmlns:x="http://schemas.
xmlns:lib="clr-namespace:
Используя определенное ранее
пространство имен XML и имя класса
пользовательского элемента управления,
можно создать элемент точно
так же, как в коде разметки создаются
объекты любого другого типа. Можно
также устанавливать его
<lib:ColorPicker Name="colorPicker" Margin="5"
ColorChanged="colorPicker_
Поскольку свойство Color использует тип данных Color, а тип Color декорирован атрибутом TypeConverter, WPF знает, как использовать ColorConverter для превращения строкового наименования цвета в соответствующий объект Color перед установкой его в свойство Color.
Код, обрабатывающий событие ColorChanged, достаточно прост:
private void colorPicker_ColorChanged(
{
txb.Text = e.NewValue.ToString();
}
На этом создание пользовательского элемента управления завершено.
ПОДДЕРЖКА КОМАНД
Многие элементы управления
обладают встроенной поддержкой команд.
Добавить такую поддержку к
- Добавить привязки команд, которые свяжут элемент управления с определенными командами. В результате элемент сможет реагировать на команды без необходимости в дополнительном кодировании.
- Создать новый объект RoutedUICommand для команды как статическое поле элемента управления, а затем добавить привязку к этой команде. Это позволит элементу управления автоматически поддерживать команды, еще не определенные в базовом наборе классов команд.
В следующем примере используется первый подход для добавления поддержки команды ApplicationCommands.Undo.
Чтобы поддержать средство Undo в указателе цвета, необходимо отслеживать предыдущий цвет в поле-члене класса:
private Color? previousColor;
Тот факт, что это поле допускает null-значения, имеет смысл, поскольку при первом создании элемента управления никакого предыдущего цвета еще не установлено. (Можно также программно очищать значение предыдущего цвета после действий, которые должны быть необратимыми.) Когда цвет изменяется, необходимо запомнить старое значение. Это можно сделать, добавив следующую строку в конец метода OnColorChanged():
colorPicker.previousColor = (Color)е.OldValue;
Теперь имеется инфраструктура,
необходимая для поддержки
Лучшее место для создания привязки команды — момент первоначального создания элемента управления. Например, в следующем коде для добавления привязки команды ApplicationCommands.Undo используется конструктор указателя цвета:
public ColorPickerUserControl()
{
InitializeComponent();
SetUpCommands();
}
private void SetUpCommands()
{
CommandBinding binding = new CommandBinding(
UndoCommand_Executed, UndoCommand_CanExecute);
this.CommandBindings.Add(
}
Чтобы сделать команду функциональной, понадобится обработать событие CanExecute и разрешить команду, если имеется предшествующее значение:
private void UndoCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = previousColor.HasValue;
}
И, наконец, когда команда выполняется, можно заменить цвет предыдущим значением:
private void UndoCommand_Executed (object sender, ExecutedRoutedEventArgs e)
{
this.Color = (Color)previousColor;
}
Команда Undo инициируется двумя другими способами. Можно применить стандартную клавиатурную привязку <Ctrl+Z>, когда соответствующий элемент в пользовательском элементе управления имеет фокус, или же добавить кнопку в клиентскую панель, которая будет инициировать команду.
В любом случае текущее
значение цвета отбрасывается и
применяется предыдущее. В данном
примере сохраняется только один
уровень информации отмены. Однако
легко создать стек отмены, который
будет хранить

- Wrestling And Literature Essay Research Paper The
- Wrestling Poetry In Motion Essay Research Paper
- Wrestling These Days It
- Wright Brothers Essay Research Paper The nineteenth
- Wright Brothers Essay Research Paper Wright BrothersPart
- Writer
- Writers Of The Harlem Renaissance Essay Research
- World War Ii Essay Research Paper As
- World War I Notes Essay Research Paper
- World War One Assignment
- World War TwoAmerica And Japan Essay Research
- Worn Path Essay Research Paper WORN PATHEudora
- Worst Day Of Life Essay Research Paper
- Worst Rental Car Company Essay Research Paper