Анализ элементов управления

48

После создания шаблона элемента управления (как будет показано в следующем разделе), шаблон заменяет существующий полностью. Несмотря на то что обеспечивается высокий уровень гибкости, также возникают сложности. В большинстве случаев нужно просмотреть стандартный шаблон, используемый элементом управления, прежде чем создавать собственную адаптированную версию. В некоторых случаях создаваемый шаблон элемента управления может повторять стандартный лишь с небольшими отличиями.

В документации WPF не приводится XAML-разметка стандартных шаблонов элементов управления. Однако необходимую информацию можно получить программно. Основная идея состоит в том, чтобы извлечь шаблон элемента из его свойства Template (которое определено как часть класса Control) и затем сериализовать его в XAML, используя класс XamlWriter. На рисунке показан пример программы, которая выводит все элементы управления WPF и позволяет видеть шаблоны каждого из них:

Просмотр шаблонов элементов управления 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. Копия шаблона элемента управления будет сохранена в виде ресурса, так что будет предложено выбрать описательный ключ ресурса. Также понадобится выбрать между сохранением ресурса в текущем окне или в глобальных ресурсах приложения, что позволит использовать шаблон элемента управления по всему приложению.

Пройди тесты
Лучший чат для C# программистов