Пользовательские элементы управления (User Controls) в Silverlight
ОГЛАВЛЕНИЕ
Я создал приложение, названное KeyboardControl, и в нем самыми первыми строками Page.xaml являются:
<UserControl x:Class="KeyboardControl.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="600" Height="350">
Фоновый код выглядит следующим образом:
namespace KeyboardControl
{
public partial class Page : UserControl
{
Вы, скорее всего, уже создавали пользовательские элементы управления. Вторым способом создания пользовательских элементов управления является явное добавление его к вашему приложению. Щелкнув правой кнопкой мыши по проекту и выбрав Add®New Item, вы получите диалоговое окно, которое предложит вам шаблон с пользовательским элементом управления Silverlight в качестве одной из опций.
Созданный пользовательский элемент управления (UserControl) является таким же, как Page.xaml, за исключением данного вами названия.
Вы можете добавлять пользовательские элементы управления в ваше приложение из-за ряда причин, но наиболее явной будет инкапсуляция некоторой функциональности, которую вы собираетесь многократно применять - как в данном приложении, так и в других.
Наилучшим примером использования будет создание некоторой функциональности и присвоение ее некоторому пользовательскому элементу управления и затем повторное её использование.
Создание повторно используемой функциональности
Повторное использование функциональности пригодно в том случае, если данная функциональность стоит того, чтобы ее еще раз использовали. Мы начнем c создания усовершенствованной формы. Вы можете вручную заполнить данную форму, но она также поддерживает клавишную комбинацию "быстрого вызова", в частности Ctrl-M добавляет адрес Microsoft, а Ctrl-C добавляет адрес музея компьютерной истории в Mountainview штата Калифорния.
Рисунок 4-1. Форма, заполненная посредством нажатия Control-C
Создание проекта Keyboard
Подразумевается, что вы уже прошли через все предыдущие уроки и в данных шагах разберетесь быстро
- Создание приложения
- Добавления элементов управления и добавление обработчиков событий
- Использование стилей
Поскольку настройка табличной сетки может оказаться утомительным занятием, я также включил стартовое приложение с исходным кодом (KeyboardStarter). Итак, запустите Visual Studio 2008 и создайте новое приложение, названное KeyboardControl
Далее демонстрируется блок текста с Home Address, а также все параметры табличной сетки, такие как граница и отступы. Подразумевается, что первым элементом управления будет StackPanel, поэтому мы можем с легкостью расположить элементы управления один над другим, а в пределах StackPanel у нас Grid.
<UserControl x:Class="KeyboardControl.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="500" Height="500">
<StackPanel Background="White">
<TextBlock Text="Home Address" FontFamily="Verdana" FontSize="24"
HorizontalAlignment="Left" Margin="15,0,0,0"/>
<Grid x:Name="AddressGrid" >
<!--More Here-->
</Grid>
</Border>
</StackPanel>
</UserControl>
Установите цвет фона в какой-нибудь светлый цвет (к примеру Bisque). Также границу сетки можно сделать черной. Установите в сетке 7 строк и 5 колонок так, как это показано ниже,
<Border BorderBrush="Black" BorderThickness="1" Margin="15">
<Grid x:Name="AddressGrid" Background="Bisque" >
<Grid.RowDefinitions>
<RowDefinition Height="10" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="10" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="10" />
</Grid.ColumnDefinitions>
<!--More Here-->
</Grid>
</Border>
Устанавливаем стили для элементов управления
Табличная сетка будет заполнена четырьмя подсказками и четырьмя текстовыми полями ввода, но они все настроены вручную, и пока нас это устраивает. Вы можете просто скопировать приведенный ниже код в App.xaml в теги Application.Resources. Каждый Style определяет свой TargetType (т.е., Button или TextBlock) и затем устанавливает значение для каждого свойства, стилем которого оно хочет управлять. Мы создаем всего 2 стиля - один для TextBlocks, который будет использован в качестве ярлыков, и второй - для получения информации в TextBoxes.
Все TextBlocks будут выровнены по нижнему (Bottom) и левому (Left) краю, и у них будет установлен шрифт Verdana, размер шрифта 18, а текст будет синего цвета. Данные элементы TextBlock также будут иметь отступ в 5 пикселей со всех сторон
<Application.Resources>
<Style TargetType="TextBlock" x:Key="TextBlockPrompt">
<Setter Property="VerticalAlignment" Value="Bottom" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="FontFamily" Value="Verdana" />
<Setter Property="FontSize" Value="18" />
<Setter Property="FontWeight" Value="Medium" />
<Setter Property="Foreground" Value="Blue" />
<Setter Property="Margin" Value="5" />
</Style>
Все элементы TextBoxe будут иметь черный жирный шрифт Verdana размера 18, а также будут выровнены по нижнему левому краю. TextBox будет размером 250 на 30 и также будет иметь отступ в 5 пикселей со всех сторон.
<Style TargetType="TextBox" x:Key="TextBoxStyle">
<Setter Property="FontFamily" Value="Verdana" />
<Setter Property="FontSize" Value="18" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="VerticalAlignment" Value="Bottom" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Width" Value="250" />
<Setter Property="Height" Value="30" />
<Setter Property="Margin" Value="5" />
</Style>
</Application.Resources>
Создание элементов управления в элементе Grid
Вместо добавления информации о стиле TextBlock вручную , мы можем привязать информацию об атрибуте Style при помощи "key" для определения желаемого стиля.
<TextBlock Text="Location: "
Style="{StaticResource TextBlockPrompt}"
Grid.Row="2" Grid.Column="1" />
Это равноценно
<TextBlock Text="Location: "
VerticalAlignment="Bottom"
HorizontalAlignment="Left"
FontFamily="Verdana"
FontSize="18"
FontWeight="Medium"
Foreground="Blue"
Margin="5"
Grid.Row="2" Grid.Column="1" />
Строка
Style="{StaticResource TextBlockPrompt}"
переходит в App.xaml и находит стиль, ключом которого является TextBlockPrompt и присваивает все значения разом. Точно так же мы можем присвоить все значения стиля TextBox привязанному текстовому полю.
<TextBox x:Name="Location"
Style="{StaticResource TextBoxStyle}"
Text ="Silverlight Central"
Grid.Row="2" Grid.Column="3" />
Они сопоставимы и мы можем проделать то же самое с тремя другими элементами управления.
<TextBox x:Name="Address1"
Style="{StaticResource TextBoxStyle}"
Text="100 Main Street"
Grid.Row="3" Grid.Column="3" />
<TextBlock Text="Address Line 2: "
Style="{StaticResource TextBlockPrompt}"
Grid.Row="4" Grid.Column="1" />
<TextBox x:Name="Address2"
Style="{StaticResource TextBoxStyle}"
Text="Apartment 100"
Grid.Row="4" Grid.Column="3" />
<TextBlock Text="City, State, Zip "
Style="{StaticResource TextBlockPrompt}"
Grid.Row="5" Grid.Column="1"/>
<TextBox x:Name="City"
Style="{StaticResource TextBoxStyle}"
Text="Boston, MA 01001"
Grid.Row="5" Grid.Column="3"/>
Заметьте что Text для каждого из элементов управления TextBox жестко запрограммирован и отображается соответственно коду при запуске приложения,
Рисунок 4-2. Форма с явно заданными значениями
Будет уместно упомянуть что в данном случае приложение обладает ограниченными возможностями.
Двусторонняя привязка данных
Одним из мощных способов привязки данных, который поможет привязать данные об адресе, например из базы данных, XML-файла или какого-либо другого хранилища данных, является использование бизнес-объекта в качестве посредника между хранилищем и пользовательским интерфейсом (приложением Silverlight). Давайте создадим объект для адреса, сопоставимого с данными, которые мы хотим отобразить (несмотря на то, что обычный бизнес-объект ,скорее всего, не будет так тесно связан с конкретной страницей пользовательского интерфейса). Вместо того, чтобы утомлять вас кодом, я просто приведу вам свойства Properties и их переменные в следующей таблице:
Private Member Variable | Public Property |
location | Location |
address1 | Address1 |
address2 | Address2 |
city | City |
Структура класса будет похожа на структуру используемую в уроке о привязке данных,
using System.Collections.Generic;
using System.ComponentModel;
namespace KeyboardControl
{
public class Address : INotifyPropertyChanged
{
Каждое свойство кроме своей переменной обладает своим методом для получения данных (Get) и методом установки данных (Set), которые также вызывают событие notification.
Как только вы заполните все общедоступные (public) свойства, вы сможете заменить все явно заданные значения Text Block привязанными значениями,
<TextBox x:Name="Location"
Style="{StaticResource TextBoxStyle}"
Text ="{Binding Location, Mode=TwoWay }"
Grid.Row="2" Grid.Column="3" />
<TextBox x:Name="Address1"
Style="{StaticResource TextBoxStyle}"
Text ="{Binding Address1, Mode=TwoWay }"
Grid.Row="3" Grid.Column="3" />
<TextBox x:Name="Address2"
Style="{StaticResource TextBoxStyle}"
Text ="{Binding Address2, Mode=TwoWay }"
Grid.Row="4" Grid.Column="3" />
<TextBox x:Name="City"
Style="{StaticResource TextBoxStyle}"
Text ="{Binding City, Mode=TwoWay }"
Grid.Row="5" Grid.Column="3"/>
Кое-что совсем отвлеченное
Давайте рассмотрим то, что мы имеем: форма, которая запрашивает информацию об адресе, а также объект Address, который может содержать либо предоставить данную информацию. Обратите внимание на то, что форма реализована согласно содержимому табличной сетки, названной "Address" содержащей четыре элемента TextBlocks и четыре TextBoxes. Что бы мы хотели добавить к функциональности самой формы, так это реакция на "быстрые клавиши ". При нажатии Ctrl-M нам необходимо, чтобы форма была заполнена информацией о Microsoft, а нажатием Ctrl-C хотелось бы заполнить форму адресом музея компьютерной истории
Рисунок 4-3. Здание музея компьютерной истории
События клавиатуры
Для реализации функциональности "быстрых клавиш" нам необходимо осуществить реакцию на события клавиатуры, полученные элементом StackPanel, в частности событие KeyDown. Файлы помощи Silverlight указывают, что событие KeyDown является пузырьковым событием.
Мы можем начать с простого и отвечать на любые события клавиатуры путем заполнения формы адресом компании Microsoft. Мы присвоим классу защищенный объект (private) Address, для которого мы определим память в конструкторе. Мы также будем обрабатывать стандартное событие Loaded, которое вызывается при загрузке страницы.
public partial class Page : UserControl
{
private Address theAddress;
public Page()
{
InitializeComponent();
theAddress = new Address();
Loaded += new RoutedEventHandler(Page_Loaded);
}
В Page_Loaded мы хотим создать обработчик события для KeyDown при регистрации любого события клавиши для любого элемента управления в пределах табличной сетки, которую, как вы помните, мы назвали AddressGrid в Page.xaml следующим образом,
<Grid x:Name="AddressGrid" Background="Bisque" >
При сохранении страницы Page.xaml данный определитель мгновенно будет доступен в фоновом коде, и мы можем получить доступ к его свойствам и событиям, включая событие KeyDown,
Рисунок 4-4. Определение в .xaml для последующего использования в фоновом коде
void Page_Loaded(object sender, RoutedEventArgs e)
{
AddressGrid.KeyDown += new KeyEventHandler(AddressGrid_KeyDown);
}
Visual Studio 2008 предложит вам создать программную оболочку для обработчика события, что нам как раз и нужно. На данный момент мы осуществим реакцию на KeyDown, всё равно для какой клавиши - мы заполним форму адресом компании Microsoft.
Данный подход можно назвать по-разному, но я уверен что изменив меньше вещей между тестовыми запусками, нам придется исследовать меньше кода в случае, если он не будет работать.
void AddressGrid_KeyDown(object sender, KeyEventArgs e)
{
theAddress.Location = "Microsoft";
theAddress.Address1 = "One Microsoft Way";
theAddress.Address2 = "Building 10";
theAddress.City = "Redmond, WA 98052";
this.DataContext = theAddress;
}
Обратите внимание на то, что мы завершаем событие установкой DataContext страницы в только что заполненный объект Address. Результатом будет объект для привязки к элементам управления. При запуске приложения ничего не произойдет. Щелкните в пределах любого из текстовых полей и затем нажмите любую клавишу и форма будет заполнена. Легче всего будет нажать клавишу shift!
Рисунок 4-5. Нажатие любой клавиши выполнит заполнение формы
Заполнение форм нажатием комбинации клавиш
Нам необходимо заполнить форму адресом компании Address при нажатии Control-M, и адресом музея при нажатии Control-C. Нам необходимо кое-что поменять в обработчике события,
void AddressGrid_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.C && Keyboard.Modifiers == ModifierKeys.Control)
{
theAddress.Location = "The Computer History Museum ";
theAddress.Address1 = "No Longer in Boston!";
theAddress.Address2 = "1401 N. Shoreline Blvd";
theAddress.City = "Mountain View, CA 94043";
}
if (e.Key == Key.M && Keyboard.Modifiers == ModifierKeys.Control)
{
theAddress.Location = "Microsoft";
theAddress.Address1 = "One Microsoft Way";
theAddress.Address2 = "Building 10";
theAddress.City = "Redmond, WA 98052";
}
this.DataContext = theAddress;
}
Небольшое изменение ширины текстовых полей в Page.xaml (изменив Width с 500 на 600) и метода установки свойства Width стиля TextBox в App.xaml, (изменив значение 250 на 350) и у нас будет достаточно места для помещения фразы "The Computer History Museum"
<Style TargetType="TextBox" x:Key="TextBoxStyle">
<Setter Property="Width" Value="350" />
</Style>
Запустите приложение и в любом месте формы нажмите Control-C, а затем Control-M, и после опять Control-C. Весело, не правда ли?
Рисунок 4-6. Форма после нажатия Control-C
Установив точку остановки в месте чтения клавиши, вы сможете увидеть проверку на нажатие комбинации Control-C либо наведя курсор на значения, либо просмотрев окошко Watch (или QuickWatch),
Рисунок 4-7. Просмотр содержимого EventArgs используя отладчик
Пользовательский элемент управления (User Control)
Это будет достаточно полезным дополнением. На самом деле, нам наверняка понадобится наличие домашнего адреса (Home Address), рабочего адреса (Work Address) и адреса выставления счета (Billing Address), и неплохо было бы иметь комбинацию клавиш для каждого из адресов. Это можно реализовать двумя способами.
- Продублируйте xaml и фоновый код для каждого экземпляра (home, work, billing)
- Перестройте стандартный xaml и поддерживающий код в элемент управления
Чтобы не терять много времени, мы будем использовать второй способ, поскольку нам не терпится создать пользовательский элемент управления (UserControl). Для начала, вам надо будет щелкнуть правой кнопкой мыши по проекту и выбрать Add. В пределах секции шаблонов диалогового окна выберите Silverlight User Control и назовите новый элемент управления Address.xaml
Рисунок 4-8. Создание пользовательского элемента управления Address
К вашему проекту будут добавлены два файла
- Address.xaml
- Address.xaml.cs
Address.xaml выглядит очень знакомо при открытии.
<UserControl x:Class="KeyboardFun.Address"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
</Grid>
</UserControl>
It is, essentially, the same as Page.xaml, except the names have been changed to protect the innocent namespace.\
Реализация элемента управления
Оставшая часть является делом техники. Вернитесь к Page.xaml и вычерпните Border и все что к нему прилагается. Простейшим способом будет сворачивание элемента управления Border (если режим outlining выключен в Visual Studio 2008, то вы можете просто включить его из меню Edit в режиме дизайнера либо просмотра кода),
Рисунок 4-9. Использование Outlining
Сверните Border и затем используйте Control-X чтобы вырезать код из текущей позиции и Control-V для вставки в новом файле .xaml, заменив табличную сетку, созданную там Visual Studio 2008
Обратите внимание на то, что ваш элемент управления находится в правильном месте, но немного узок. Удалите атрибуты ширины (Width) и высоты (Height) и внешнего пользовательского элемента управления,
<UserControl x:Class="UserControlDemo.AddressUserControl"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
> <!--no specific size-->
<Border BorderBrush="Black" BorderThickness="1" Margin="15">
Как только вы удалите их, элемент управления будет выровнен по центру, а также расширен для того, чтобы вместить в себя элементы управления,
Рисунок 4-10. Автоматическое изменение размера пользовательского элемента управления
Добавление кода
Все последующие шаги выполняются в AddressUserControl.xaml.cs:
- Добавьте переменную экземпляра Address так же, как вы это делали раньше
- В конструкторе выделите память для объекта Address и настройте обработчик события Loaded
- В реализации Page_Loaded добавьте новый обработчик для события KeyDown табличной сетки
public partial class AddressUserControl : UserControl
{
private Address theAddress = null;
public AddressUserControl()
{
InitializeComponent();
theAddress = new Address();
Loaded += new RoutedEventHandler(Page_Loaded);
}
void Page_Loaded(object sender, RoutedEventArgs e)
{
AddressGrid.KeyDown += new KeyEventHandler(AddressGrid_KeyDown);
} - Реализация AddressGrid.KeyDown была вырезана и может быть вставлена в Page.xaml.cs,
void AddressGrid_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.C && Keyboard.Modifiers == ModifierKeys.Control)
{
theAddress.Location = "The Computer History Museum ";
theAddress.Address1 = "No Longer in Boston!";
theAddress.Address2 = "1401 N. Shoreline Blvd";
theAddress.City = "Mountain View, CA 94043";
}
if (e.Key == Key.M && Keyboard.Modifiers == ModifierKeys.Control)
{
theAddress.Location = "Microsoft";
theAddress.Address1 = "One Microsoft Way";
theAddress.Address2 = "Building 10";
theAddress.City = "Redmond, WA 98052";
}
this.DataContext = theAddress;
} // end KeyDown event handler
} // end class AddressUserControl
Использование пользовательского элемента управления
Мы завершили создание пользовательского элемента управления, но от него нет толку пока вы его не расположите в файле Page.xaml. Вот как надо это сделать:
- Сохраните все файлы
- В самой верхней части Page.xaml добавьте пространство имен для вашей страницы с префиксом на ваш вкус (я просто использовал jl). Intellisense очень старается помочь мне в этом.
Рисунок 4-11. Добавление пространства именЗавершите все следующей строкой:
xmlns:jl="clr-namespace:UserControlDemo"
- Теперь вы можете заменить ваш пользовательский элемент управления для всего содержимого Border в Page.xaml. Если вы еще не удалили Border, то удалите и замените своим элементом управления, точно также как вы вставили бы любой другой элемент управления в StackPanel,
<jl:AddressUserControl x:Name="HomeAddress" />
Многократное использование
Давайте добавим еще один пользовательский элемент управления к StackPanel и изменим подсказки таким образом, чтобы у нас было следующее,
<UserControl x:Class="UserControlDemo.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:jl="clr-namespace:UserControlDemo"
Width="600" Height="500">
<StackPanel Background="White">
<TextBlock Text="Event Address" FontFamily="Verdana" FontSize="24"
HorizontalAlignment="Left" Margin="15,0,0,0"/>
<jl:AddressUserControl x:Name="HomeAddress" />
<TextBlock Text="Billing Address" FontFamily="Verdana" FontSize="24"
HorizontalAlignment="Left" Margin="15,0,0,0"/>
<jl:AddressUserControl x:Name="BillingAddress" />
</StackPanel>
</UserControl>
Обратите внимание, что добавление второго экземпляра AddressUserControl требует только наличия двух различных названий. До того, как вы запустите программу, исследуйте Page.xaml.cs,
using System.Windows.Controls;
namespace UserControlDemo
{
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
}
}
}
Вся логика была экспортирована и инкапсулирована в пользовательском элементе управления. Вы можете добавить 2 (или 20) пользовательских элемента управления Address в ваш интерфейс, не написав и строки кода. При этом, запустив программу, каждый элемент будет работать независимо и каждый поддерживает комбинацию клавиш с клавишей Ctrl,
Рисунок 4-12. Два экземпляра одного пользовательского элемента управления
Заключение
Если вам не нужен полноценный специализированный элемент управления (который можно предоставить клиенту), преобразование пользовательского интерфейса и поддерживаемого кода в пользовательские элементы управления (User Controls) является практически тривиальным и очень пригодным, предоставляя вам более функциональный код.
Обратите внимание на то, что исходный код для данного урока предоставлен в трех частях:
- KeyboardStarter предоставляет вам табличную сетку и основные элементы управления и стили с которыми вы уже знакомы
- KeyboardControl является промежуточным приложением с обработчиком события KeyDown
- UserControlDemo является конечным продуктом, где логика обработчика события KeyDown была переработана а в пользовательский элемент управления (User control).
Jesse Liberty