Анализ элементов управления
48WPF --- Шаблоны и пользовательские элементы управления WPF --- Анализ элементов управления
После создания шаблона элемента управления (как будет показано в следующем разделе), шаблон заменяет существующий полностью. Несмотря на то что обеспечивается высокий уровень гибкости, также возникают сложности. В большинстве случаев нужно просмотреть стандартный шаблон, используемый элементом управления, прежде чем создавать собственную адаптированную версию. В некоторых случаях создаваемый шаблон элемента управления может повторять стандартный лишь с небольшими отличиями.
В документации WPF не приводится XAML-разметка стандартных шаблонов элементов управления. Однако необходимую информацию можно получить программно. Основная идея состоит в том, чтобы извлечь шаблон элемента из его свойства Template (которое определено как часть класса Control) и затем сериализовать его в XAML, используя класс XamlWriter. На рисунке показан пример программы, которая выводит все элементы управления WPF и позволяет видеть шаблоны каждого из них:
Секрет построения этого приложения состоит в интенсивном использовании рефлексии — API-интерфейса .NET для исследования типов. Когда главное окно этого приложения загружается в первый раз, оно сканирует все типы в основной сборке PresentationFramework.dll (в которой определен класс Control). Затем эти типы добавляются в коллекцию, которая сортируется по именам типов, и результирующая коллекция привязывается к списку.
Всякий раз, когда элемент управления выбирается в списке, в текстовом поле справа отображается соответствующий шаблон элемента управления. Этот шаг требует чуть больше работы. Первая сложность состоит в том, что шаблон элемента управления равен null, пока элемент не отобразится в окне. Используя рефлексию, код пытается создать экземпляр элемента управления и добавить его в текущее окно (хотя с Visibility, равным Collapse, так что он остается невидимым).
Вторая сложность в том, что актуальный объект ControlTemplate нужно преобразовать в знакомую XAML-разметку. Эту задачу решает статический метод XamlWriter.Save(), а в коде используются объекты XamlWriter и XmlWriterSetting для обеспечения отступов в XAML-разметке, что улучшит его читабельность. Весь код помещен в блок обработки исключений. Здесь перехватываются все проблемы, возникающие из-за элемента управления, который не может быть создан и добавлен в Grid (например, другой Window или Page):
<Grid Name="grid" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<ListBox Name="lbx" Margin="5" SelectionChanged="lbx_SelectionChanged"></ListBox>
</ScrollViewer>
<TextBox TextWrapping="Wrap" IsReadOnly="True" Margin="5"
Name="txb" Grid.Column="1" VerticalScrollBarVisibility="Auto"></TextBox>
</Grid>
using System.Reflection;
using System.Xml;
using System.Windows.Markup;
// ...
// В классе MainWindow
private void mainWindow_Loaded(object sender, RoutedEventArgs e)
{
Type typeControl = typeof(Control);
List<Type> myTypes = new List<Type>();
// Ищем все типы в сборке
Assembly assembly = Assembly.GetAssembly(typeof(Control));
foreach (Type type in assembly.GetTypes())
{
if (type.IsSubclassOf(typeControl) && !type.IsAbstract && type.IsPublic)
{
myTypes.Add(type);
}
// отсортируем список
myTypes.Sort(new TypeComparer());
lbx.ItemsSource = myTypes;
lbx.DisplayMemberPath = "Name";
}
}
private void lbx_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
try
{
// Get the selected type.
Type type = (Type)lbx.SelectedItem;
// Instantiate the type.
ConstructorInfo info = type.GetConstructor(System.Type.EmptyTypes);
Control control = (Control)info.Invoke(null);
Window win = control as Window;
if (win != null)
{
// Create the window (but keep it minimized).
win.WindowState = System.Windows.WindowState.Minimized;
win.ShowInTaskbar = false;
win.Show();
}
else
{
// Add it to the grid (but keep it hidden).
control.Visibility = Visibility.Collapsed;
grid.Children.Add(control);
}
// Get the template.
ControlTemplate template = control.Template;
// Get the XAML for the template.
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
StringBuilder sb = new StringBuilder();
XmlWriter writer = XmlWriter.Create(sb, settings);
XamlWriter.Save(template, writer);
// Display the template.
txb.Text = sb.ToString();
// Remove the control from the grid.
if (win != null)
{
win.Close();
}
else
{
grid.Children.Remove(control);
}
}
catch (Exception err)
{
txb.Text = "<< Error generating template: " + err.Message + ">>";
}
}
// ...
public class TypeComparer : IComparer<Type>
{
public int Compare(Type x, Type y)
{
return x.Name.CompareTo(y.Name);
}
}
Это приложение было бы несложно расширить так, чтобы можно было редактировать шаблон в текстовом поле, преобразовывать обратно в объект ControlTemplate (используя XamlWriter) и назначать его элементу управления для просмотра эффекта. Однако тестирование и совершенствование шаблонов элементов управления будет проводиться за счет их вставки в реальное окно.
При работе с Expression Blend можно воспользоваться удобным средством, которое позволяет редактировать шаблон любого элемента управления. (Формально это средство захватывает шаблон по умолчанию, создает копию его для элемента управления и позволяет редактировать эту копию). Щелкните правой кнопкой мыши на элементе управления в окне визуального конструктора и выберите в контекстном меню пункт Edit Control Parts (Template) --> Edit a Copy. Копия шаблона элемента управления будет сохранена в виде ресурса, так что будет предложено выбрать описательный ключ ресурса. Также понадобится выбрать между сохранением ресурса в текущем окне или в глобальных ресурсах приложения, что позволит использовать шаблон элемента управления по всему приложению.