Создание элемента управления, лишенного внешнего вида

96

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

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

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

Рефакторинг кода указателя цвета

Превратить рассмотренный указатель цвета в элемент управления без внешнего вида не так трудно. Первый шаг прост — нужно всего лишь изменить объявление класса, как показано ниже:

public class ColorPicker : System.Windows.Controls.Control
{ ... }

В этом примере класс ColorPicker наследуется от Control. Класс FrameworkElement не подходит, поскольку указатель цвета требует взаимодействия с пользователем, а другие высокоуровневые классы не могут точно описать его поведение. Например, указатель цвета не позволяет вставлять в него другое содержимое, а потому ContentControl тоже не годится.

Код внутри класса ColorPicker точно такой же, как код пользовательского элемента управления (за исключением того факта, что из конструктора понадобится удалить вызов InitializeComponent()). Вы следуете тому же подходу для определения свойств зависимости и маршрутизируемых событий. Единственное отличие связано с необходимостью сообщения WPF о том, что для класса элемента управления будет предоставлен новый стиль. Этот стиль будет обеспечен новым шаблоном элемента. (Если пропустить этот шаг, будет использован шаблон, определенный в базовом классе.)

Чтобы сообщить WPF о том, что предоставляется новый стиль, следует вызвать метод OverrideMetadata() в статическом конструкторе класса. Этот метод вызывается на свойстве DefaultStyleKeyProperty, которое является свойством зависимости, определяющим стиль по умолчанию для элемента управления. Необходимый код выглядит так:

DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorPicker), 
        new FrameworkPropertyMetadata(typeof(ColorPicker)));

Можно предоставить другой тип, если нужно использовать шаблон другого класса элемента управления, но почти всегда для каждого из собственных пользовательских элементов управления будет создаваться специфический стиль.

Рефакторинг кода разметки указателя цвета

После добавления вызова OverrideMetadata понадобится просто подключить правильный стиль. Этот стиль должен быть помещен в словарь ресурсов по имени generic.xaml, который следует сохранить в папке Themes проекта. Таким образом, этот стиль будет распознан как стиль по умолчанию для элемента управления. Для добавления файла generic.xaml выполните следующие шаги:

На рисунке можно видеть файл generic.xaml в папке Themes:

Приложение WPF и библиотека классов

Часто библиотека пользовательских элементов управления содержит несколько таких элементов. Чтобы держать их стили отдельно для облегчения редактирования, файл generic.xaml часто использует слияние словарей ресурсов. В следующей разметке показано содержимое файла generic.xaml, который извлекает ресурсы из ресурсного словаря ColorPicker.xaml в той же папке Themes библиотеки элементов управления по имени CustomControls:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/ColorPicker;component/themes/ColorPicker.xaml"></ResourceDictionary>
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

Стиль пользовательского элемента управления должен использовать атрибут TargetType для автоматического присоединения себя к указателю цвета.

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

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

Принимая во внимание перечисленные соображения, можно создать следующий шаблон для указателя цвета:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CustomControls">
  <Style TargetType="{x:Type local:ColorPicker}">
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="{x:Type local:ColorPicker}">
          <Border Background="{TemplateBinding Background}"
                  BorderBrush="{TemplateBinding BorderBrush}"
                  BorderThickness="{TemplateBinding BorderThickness}">
            <Grid>
              <Grid.RowDefinitions>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
              </Grid.RowDefinitions>
              <Grid.ColumnDefinitions>
                <ColumnDefinition></ColumnDefinition>
                <ColumnDefinition Width="Auto"></ColumnDefinition>
              </Grid.ColumnDefinitions>

              <Slider Name="PART_RedSlider" Minimum="0" Maximum="255" 
                       Margin="{TemplateBinding Padding}"></Slider>
              <Slider Grid.Row="1" Name="PART_GreenSlider" Minimum="0" Maximum="255"
                       Margin="{TemplateBinding Padding}"></Slider>

              <Slider Grid.Row="2" Name="PART_BlueSlider" Minimum="0" Maximum="255"
                       Margin="{TemplateBinding Padding}"></Slider>

              <Rectangle Grid.Column="1" Grid.RowSpan="3"
                         Margin="{TemplateBinding Padding}"
                         Width="50" Stroke="Black" StrokeThickness="1">
                <Rectangle.Fill>
                  <SolidColorBrush Color="{Binding Path=Color,
                       RelativeSource={RelativeSource TemplatedParent}}"></SolidColorBrush>
                </Rectangle.Fill>
              </Rectangle>

            </Grid>
            
          </Border>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>

Как видите, некоторые выражения привязки заменены расширением TemplateBinding. Другие по-прежнему используют расширение Binding, но имеют свойство RelativeSource, указывающее на родителя шаблона (пользовательский элемент управления). Хотя и TemplateBinding, и Binding с RelativeSource из TemplatedParent служат одной и той же цели — извлечению данных из свойств пользовательского элемента управления, все же облегченный TemplateBinding более предпочтителен. Однако он не будет работать, если нужна двунаправленная привязка (как в случае ползунков) или когда осуществляется привязка к свойству класса, унаследованного от Freezable (вроде SolidColorBrush).

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