Общие сведения о разработке элементов управления
Расширяемость модели элементов управления Windows Presentation Foundation (WPF) значительно уменьшает необходимость создания новых элементов управления. Однако в некоторых случаях может потребоваться создать пользовательский элемент управления. В этом разделе обсуждаются функции, которые уменьшают необходимость создания пользовательских элементов управления, а также различные модели создания элементов управления в Windows Presentation Foundation (WPF). Также здесь демонстрируется создание нового элемента управления.
Альтернативы написанию нового элемента управления
Исторически сложилось, что если нужно настроить вид существующего элемента управления, то ограничиваются изменением его стандартных свойств, таких как цвет фона, ширина границы и размер шрифта. Если необходимо расширить внешний вид или поведение элемента управления за пределы этих предопределенных параметров, то необходимо создать новый элемент управления, как правило, путем наследования от существующего элемента управления и переопределения метода, ответственного за отрисовку элемента управления. Кроме того, WPF позволяет настраивать существующие элементы управления с помощью модели форматированного содержимого, стилей, шаблонов и триггеров. Ниже представлены примеры использования этих функций для создания настраиваемых и согласованных функциональных возможностей без необходимости создания нового элемента управления.
Форматированное содержимое. Многие стандартные элементы управления WPF поддерживают форматированное содержимое. Например, свойство содержимого Button имеет тип Object, поэтому теоретически все может отображаться на Button. Чтобы в кнопке отображалось изображение и текст, можно добавить изображение и TextBlock для StackPanel и назначить StackPanel для Content свойство. Поскольку элементы управления могут отображать визуальные элементы WPF и произвольные данные, это уменьшает необходимость создания нового элемента управления или изменения существующего для поддержки сложной визуализации. Дополнительные сведения о модели содержимого для Button и других моделях содержимого в WPF, см. в разделе модель содержимого WPF.
Стили. Объект Style — это совокупность значений, представляющих свойства для элемента управления. С помощью стилей можно создать повторно используемое представление нужного внешнего вида и поведения элемента управления без написания нового элемента управления. Например, предположим, что все ваши TextBlock элементам управления иметь красный, сделанные шрифтом Arial с размером шрифта из 14. Можно создать стиль как ресурс и задать соответствующие свойства. Каждый TextBlock добавить в приложение будет выглядеть одинаково.
Шаблоны данных. Объект DataTemplate позволяет настроить способ отображения данных в элементе управления. Например DataTemplate может использоваться для указания способа отображения данных в ListBox. Пример см. в разделе Общие сведения о шаблонах данных. В дополнение к настройке внешнего вида данных, DataTemplate может включать элементы пользовательского интерфейса, который обеспечивает большую гибкость в пользовательских интерфейсах. Например, с помощью DataTemplate, можно создать ComboBox в котором каждый элемент содержит типа "флажок".
Шаблоны элементов управления. Многие элементы управления в WPF использовать ControlTemplate для определения структуры элемента управления и внешний вид, который отделяет внешний вид элемента управления от функциональности элемента управления. Можно существенно изменить внешний вид элемента управления путем переопределения его ControlTemplate. Предположим, что нужен элемент управления, который выглядит как светофор. Этот элемент управления имеет простой пользовательский интерфейс и функциональные возможности. Элемент управления состоит из трех кругов, из которых одновременно загорается только один. Позже вы можете осознать, RadioButton предлагает функциональные возможности выбора только один раз, но внешний вид по умолчанию RadioButton похож индикаторы на светофор. Так как RadioButton использует шаблон элемента управления для определения внешнего вида, он может легко переопределить ControlTemplate согласно требованиям элемента управления и использовать переключатели, чтобы сделать Светофор.
Note
Несмотря на то что RadioButton можно использовать DataTemplate, DataTemplate не хватает в этом примере. DataTemplate Определяет внешний вид содержимого элемента управления. В случае использования RadioButton, содержимое отображается справа от круга, который указывает ли RadioButton выбран. В примере светофора переключатель должен быть тем кругом, который может "загораться". Так как требования к внешнему вида для светофора отличаются от внешнего вида по умолчанию RadioButton, необходимо переопределить ControlTemplate. В целом DataTemplate используется для определения содержимого (или данных) элемента управления, а также ControlTemplate используется для определения того, как структурирован элемент управления.
Триггеры. Объект Trigger позволяет динамически изменять внешний вид и поведение элемента управления без создания нового элемента управления. Например, предположим, имеется несколько ListBox элементы управления в приложении и элементы в каждом ListBox должен выделяться полужирным красным при их выборе. Очевидным может быть попытка создать класс, наследующий от ListBox и переопределить OnSelectionChanged способ изменения внешнего вида выбранного элемента, но более эффективный подход — добавить триггер к стилю ListBoxItem , изменяет внешний вид Выбранный элемент. Триггер позволяет изменять значения свойств или выполнять действия в зависимости от значения свойства. EventTrigger Позволяет выполнять действия при возникновении события.
Дополнительные сведения о стилях, шаблонах и триггерах см. в разделе Использование стилей и шаблонов.
В целом, если элемент управления отражает функциональность существующего элемента управления, но должен выглядеть по-другому, сначала следует рассмотреть возможность использования какого-либо из методов, описанных в этом разделе, для изменения внешнего вида существующего элемента.
Модели для создания элементов управления
Модель форматированного содержимого, стили, шаблоны и триггеры уменьшают необходимость создания новых элементов управления. Тем не менее, если необходимо создать новый элемент управления, важно понимать различные модели создания элементов управления в WPF. WPF предоставляет три общих модели для создания элементов управления, каждый из которых имеет собственный набор функций и уровень гибкости. Базовыми классами для трех моделей UserControl, Control, и FrameworkElement.
Создание производных классов от UserControl
Самый простой способ создания элемента управления в WPF является наследование от UserControl. При создании элемента управления, который наследует от UserControl, вы добавляете существующие компоненты к UserControlкомпонентам имя и ссылки на обработчики событий в XAML. Затем можно ссылаться на именованные элементы и определять обработчики событий в коде. Эта модель разработки очень схожа с моделью, используемой для разработки приложений в WPF.
При правильном, построении UserControl можно воспользоваться преимуществами форматированного содержимого, стилей и триггеров. Тем не менее если элемент управления наследуется из UserControl, люди, использующие элемент управления не будет иметь возможность использовать DataTemplate или ControlTemplate для настройки внешнего вида. Это необходимо для являются производными от Control класс или один из его производных классов (отличное от UserControl) для создания пользовательского элемента управления, поддерживающего шаблоны.
Преимущества использования производного класса от UserControl
Рассмотрите возможность наследования от UserControl применимости всем следующим условиям:
Нужно создать элемент управления аналогично созданию приложения.
Элемент управления состоит только из существующих компонентов.
Не нужно поддерживать сложные настройки.
Создание производного от элемента управления
Наследование от Control класс является модель, используемая в большинстве существующих WPF элементов управления. При создании элемента управления, который наследует от Control класса, его внешний вид определяется с помощью шаблонов. Таким образом, можно отделить рабочую логику от визуального представления. Также можно обеспечить разделение пользовательского интерфейса и логики с помощью команд и привязок вместо событий и использования ссылок на элементы в ControlTemplate по возможности. Если пользовательский Интерфейс и логика элемента управления правильно разделены, пользователи элемента управления могут переопределить элемент управления ControlTemplate для настройки внешнего вида. Несмотря на то, что построение пользовательского Control не так просто, как строительные UserControl, пользовательский Control обеспечивает наибольшую гибкость.
Преимущества использования производного от элемента управления
Рассмотрите возможность наследования от Control вместо использования UserControl класса, если выполняется одно из следующих условий:
Внешний вид элемента управления нужно настраивать через ControlTemplate.
Элемент управления должен поддерживать различные темы.
Создание производного от FrameworkElement
Элементы управления, которые являются производными от UserControl или Control основываются на сочетании существующих элементов. Во многих сценариях это приемлемое решение, так как любой объект, который наследует FrameworkElement может находиться в ControlTemplate. Однако бывают случаи, когда для внешнего вида элемента управления требуется больше, чем функциональность простой композиции элементов. В таких случаях компонент создание на основе FrameworkElement является правильным выбором.
Существует два стандартных метода построения FrameworkElement-компонентов на основе: прямая отрисовка и пользовательская композиция элемента. Прямая отрисовка включает переопределение OnRender метод FrameworkElement и предоставляя DrawingContext операций, которые явно определяют графические параметры компонента. Это метод, используемый Image и Border. Пользовательская композиция элемента включает использование объектов типа Visual для создания внешнего вида компонента. Например, см. раздел Использование объектов DrawingVisual. Track является примером элемента управления в WPF , использует пользовательскую композицию элемента. Можно также комбинировать прямую отрисовку и пользовательскую композицию элемента в одном элементе управления.
Преимущества использования производного от FrameworkElement
Рассмотрите возможность наследования от FrameworkElement Если какой-либо из указанных ниже условий:
Требуется точный контроль над внешним видом элемента управления помимо того, который обеспечивается простой композицией элемента.
Необходимо определить внешний вид элемента управления путем определения собственной логики отрисовки.
Требуется построить существующие элементы нестандартными способами, выходящими за рамки возможностей в средстве UserControl и Control.
Основы создания элементов управления
Как обсуждалось выше, одной из наиболее мощных функций WPF является возможность выхода за пределы задания базовых свойств элемента управления, чтобы изменить его внешний вид и поведение, но без необходимости создания пользовательского элемента управления. Функции стилей, привязки данных и триггеров предоставляются системой свойств WPF и системой событий WPF. В следующих разделах описаны некоторые рекомендации, которым нужно следовать независимо от модели, используемой для создания пользовательского элемента управления. Это необходимо, чтобы пользователи вашего пользовательского элемента управления могли использовать эти функции точно так же, как для элемента управления, входящего в WPF.
Использование свойств зависимостей
Если свойство является свойством зависимостей, то можно сделать следующее:
Установить свойство в стиле.
Привязать свойство к источнику данных.
Использовать динамический ресурс в качестве значения свойства.
Анимировать свойство.
Если свойство элемента управления должно поддерживать подобную функциональность, то следует реализовать его как свойство зависимостей. В следующем примере определяется свойство зависимостей с именем Value
следующим способом:
Определение DependencyProperty идентификатор с именем
ValueProperty
какpublic
static
readonly
поля.Зарегистрируйте имя свойства в системе свойств с помощью метода DependencyProperty.Register, чтобы указать следующее:
Имя свойства.
Тип свойства.
Тип, к которому принадлежит это свойство.
Метаданные для свойства. Метаданные содержат значение свойства по умолчанию, CoerceValueCallback и PropertyChangedCallback.
Определите свойство программы-оболочки CLR с именем
Value
, которое используется для регистрации свойства зависимостей, путем реализации методов доступаget
иset
свойства. Обратите внимание, чтоget
иset
вызывать только методы доступа GetValue и SetValue соответственно. Рекомендуется, чтобы методы доступа свойств зависимостей не содержали дополнительной логики, так как клиенты и WPF может обойти методы доступа и вызов GetValue и SetValue напрямую. Например, если свойство привязано к источнику данных, то метод доступаset
свойства не вызывается. Вместо добавления дополнительной логики для получения и установки методов доступа, используйте ValidateValueCallback, CoerceValueCallback, и PropertyChangedCallback реагировать на них или проверьте значение, при его изменении делегаты. Дополнительные сведения об этих обратных вызовах см. в разделе Проверка и обратные вызовы свойства зависимостей.Определите метод для CoerceValueCallback с именем
CoerceValue
.CoerceValue
гарантирует, чтоValue
больше или равноMinValue
и меньше или равноMaxValue
.Определите метод для PropertyChangedCallbackс именем
OnValueChanged
.OnValueChanged
Создает RoutedPropertyChangedEventArgs<T> объекта и готовится кValueChanged
перенаправленного события. Перенаправляемые события рассматриваются в следующем разделе.
/// <summary>
/// Identifies the Value dependency property.
/// </summary>
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value", typeof(decimal), typeof(NumericUpDown),
new FrameworkPropertyMetadata(MinValue, new PropertyChangedCallback(OnValueChanged),
new CoerceValueCallback(CoerceValue)));
/// <summary>
/// Gets or sets the value assigned to the control.
/// </summary>
public decimal Value
{
get { return (decimal)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private static object CoerceValue(DependencyObject element, object value)
{
decimal newValue = (decimal)value;
NumericUpDown control = (NumericUpDown)element;
newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue));
return newValue;
}
private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
NumericUpDown control = (NumericUpDown)obj;
RoutedPropertyChangedEventArgs<decimal> e = new RoutedPropertyChangedEventArgs<decimal>(
(decimal)args.OldValue, (decimal)args.NewValue, ValueChangedEvent);
control.OnValueChanged(e);
}
Дополнительные сведения см. в разделе Пользовательские свойства зависимостей.
Использование перенаправляемых событий
Аналогично тому, как свойства зависимостей расширяют представление свойств CLR дополнительной функциональностью, перенаправляемые события расширяют представление стандартных событий CLR. При создании нового элемента управления WPF рекомендуется также реализовывать событие как перенаправляемое, так как такие события поддерживают следующее поведение:
События могут обрабатываться в родительском элементе нескольких элементов управления. Если событие является событием восходящей маршрутизации, то один родительский элемент в дереве элементов может подписаться на это событие. Разработчики приложений могут использовать один обработчик для реагирования на событие нескольких элементов управления. Например, если элемент управления является частью каждого элемента в ListBox (так как он включен в DataTemplate), разработчик приложения может определить обработчик событий для события элемента управления на ListBox. Обработчик событий вызывается при возникновении события в любом элементе управления.
Перенаправленные события могут использоваться в EventSetter, что позволяет разработчикам приложений указать обработчик события в стиле.
Перенаправленные события могут использоваться в EventTrigger, что удобно для анимации свойств с помощью XAML. Более подробную информацию см. в разделе Общие сведения об эффектах анимации.
Следующий пример определяет перенаправляемое событие:
Определение RoutedEvent идентификатор с именем
ValueChangedEvent
какpublic
static
readonly
поля.Зарегистрируйте перенаправляемое событие путем вызова EventManager.RegisterRoutedEvent метод. В примере указывается следующие сведения при вызове RegisterRoutedEvent:
Имя события
ValueChanged
.Стратегия маршрутизации Bubble, что означает, что сначала вызывается обработчик событий в источнике (объект, вызывающий событие) и затем вызываются обработчики событий родительских элементов источника подряд, начиная с обработчика события для ближайшего родительский элемент.
Тип обработчика событий — RoutedPropertyChangedEventHandler<T>, созданный с Decimal типа.
Тип — владелец события —
NumericUpDown
.
Объявите общее событие с именем
ValueChanged
, которое включает объявления метода доступа к событию. В примере вызывается AddHandler вadd
объявление метода доступа и RemoveHandler вremove
объявление метода доступа для использования WPF служб событий.Создайте защищенный виртуальный метод с именем
OnValueChanged
, вызывающий событиеValueChanged
.
/// <summary>
/// Identifies the ValueChanged routed event.
/// </summary>
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
"ValueChanged", RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler<decimal>), typeof(NumericUpDown));
/// <summary>
/// Occurs when the Value property changes.
/// </summary>
public event RoutedPropertyChangedEventHandler<decimal> ValueChanged
{
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }
}
/// <summary>
/// Raises the ValueChanged event.
/// </summary>
/// <param name="args">Arguments associated with the ValueChanged event.</param>
protected virtual void OnValueChanged(RoutedPropertyChangedEventArgs<decimal> args)
{
RaiseEvent(args);
}
Дополнительные сведения см. в разделах Общие сведения о перенаправляемых событиях и Создание пользовательских перенаправляемых событий.
Использование привязки
Для отделения пользовательского интерфейса от логики элемента управления можно использовать привязку данных. Это особенно важно в том случае, если внешний вид элемента управления с помощью ControlTemplate. При использовании привязки данных можно избавиться от необходимости ссылаться на определенные части пользовательского интерфейса из кода. Рекомендуется избегать ссылок на элементы в ControlTemplate потому, что если код ссылается на элементы в ControlTemplate и ControlTemplate изменяется, указанный ссылками элемент должен быть включен в новом ControlTemplate.
В следующем примере обновляется TextBlock из NumericUpDown
элемента управления, присвоение имени и ссылается на текстовое поле по имени в коде.
<Border BorderThickness="1" BorderBrush="Gray" Margin="2"
Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">
<TextBlock Name="valueText" Width="60" TextAlignment="Right" Padding="5"/>
</Border>
private void UpdateTextBlock()
{
valueText.Text = Value.ToString();
}
Следующий пример использует привязку для достижения такого же результата.
<Border BorderThickness="1" BorderBrush="Gray" Margin="2"
Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">
<!--Bind the TextBlock to the Value property-->
<TextBlock
Width="60" TextAlignment="Right" Padding="5"
Text="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:NumericUpDown}},
Path=Value}"/>
</Border>
Дополнительные сведения о привязке данных см. в разделе Общие сведения о привязке данных.
Разработка для конструкторов
Чтобы получить поддержку пользовательских элементов управления WPF в Конструктор WPF для Visual Studio (например, редактирование свойства с помощью окна "Свойства"), следуйте приведенным ниже рекомендациям. Дополнительные сведения о разработке для Конструктор WPF, см. в разделе конструктора XAML в Visual Studio.
Свойства зависимостей
Следует реализовать методы доступа CLR get
и set
, как описано ранее в разделе "Использование свойств зависимостей". Конструкторы могут использовать программы-оболочки для обнаружения свойства зависимостей, но им, как WPF и клиентам элемента управления, не требуется вызывать методы доступа при получении или настройке свойства.
Вложенные свойства
При реализации вложенных свойств в пользовательских элементах управления учитывайте следующие рекомендации:
У
public
static
readonly
DependencyProperty формы PropertyNameProperty
, создании с помощью RegisterAttached метод. Имя свойства, которое передается RegisterAttached должно соответствовать PropertyName.Реализуйте пару методов CLR
public
static
с именемSet
PropertyName иGet
PropertyName. Оба метода должны принимать производный класс от DependencyProperty качестве первого аргумента. МетодSet
PropertyName также принимает аргумент, тип которого соответствует зарегистрированному типу данных для свойства. МетодGet
PropertyName должен возвращать значение такого же типа. Если методSet
PropertyName отсутствует, свойство отмечается как "только для чтения".Set
*PropertyName иGet
PropertyName должны перенаправляться непосредственно в GetValue и SetValue методы от зависимости целевого объекта, соответственно.DРазработчики могут получить доступ к вложенному свойству, вызвав программу-оболочку метода или с помощью прямого вызова целевого объекта зависимостей.
Дополнительные сведения о вложенных свойствах см. в разделе Общие сведения о вложенных свойствах.
Определение и использование общих ресурсов
Можно включить элемент управления в ту же сборку, что и приложение, или упаковать его в отдельную сборку, которая может использоваться в нескольких приложениях. В большинстве случаев сведения, рассматриваемые в данном разделе, применяются независимо от используемого метода. Однако есть одно отличие, о котором следует упомянуть. При помещении элемента управления в ту же сборку, что и приложение, можно добавить глобальные ресурсы в файл App.xaml. Но не сборку, содержащую только элементы управления Application объект, связанный с ним, поэтому файл App.xaml недоступно.
Приложение выполняет поиск ресурса на трех уровнях в следующем порядке:
Уровень элемента.
Система начинает с элемента, который ссылается на ресурс, а затем ищет ресурсы логического родительского элемента и так далее, пока не будет достигнут корневой элемент.
Уровень приложения.
Ресурсы, определенные в Application объекта.
Уровень темы.
Словари уровня темы хранятся в подпапке "Темы". Файлы в папке "Темы" соответствуют темам. Например, могут присутствовать файлы Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml и т. д. Также может присутствовать файл с именем generic.xaml. Когда система ищет ресурс на уровне темы, она сначала ищет его в файле конкретной темы, а затем в файле generic.xaml.
Если элемент управления находится в сборке отдельно от приложения, глобальные ресурсы необходимо поместить на уровень элемента или на уровень темы. Оба метода имеют свои преимущества.
Определение ресурсов на уровне элемента
Общие ресурсы на уровне элемента можно определить путем создания пользовательского словаря ресурсов и его объединения со словарем ресурсов элемента управления. При использовании этого метода можно присвоить файлу ресурсов любое имя и его можно поместить в одну папку с элементами управления. Ресурсы на уровне элемента также могут использовать простые строки как ключи. В следующем примере создается LinearGradientBrush файл ресурсов с именем Dictionary1.xaml.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<LinearGradientBrush
x:Key="myBrush"
StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="Red" Offset="0.25" />
<GradientStop Color="Blue" Offset="0.75" />
</LinearGradientBrush>
</ResourceDictionary>
После определения словаря необходимо его объединить со словарем ресурсов элемента управления. Это можно сделать с помощью XAML или кода.
Следующий пример объединяет словарь ресурса с помощью XAML.
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
Недостаток этого подхода заключается в том, ResourceDictionary создается каждый раз, ссылки на объект. Например, если у вас 10 пользовательских элементов управления в библиотеке и объединение словарей общих ресурсов для каждого элемента управления с помощью XAML, будут созданы 10 идентичных ResourceDictionary объектов. Этого можно избежать, создав статический класс, который объединяет ресурсы в коде и возвращает результат в виде ResourceDictionary.
В следующем примере создается класс, который возвращает общий ResourceDictionary.
internal static class SharedDictionaryManager
{
internal static ResourceDictionary SharedDictionary
{
get
{
if (_sharedDictionary == null)
{
System.Uri resourceLocater =
new System.Uri("/ElementResourcesCustomControlLibrary;component/Dictionary1.xaml",
System.UriKind.Relative);
_sharedDictionary =
(ResourceDictionary)Application.LoadComponent(resourceLocater);
}
return _sharedDictionary;
}
}
private static ResourceDictionary _sharedDictionary;
}
В следующем примере общий ресурс объединяется с ресурсами пользовательского элемента управления в конструкторе элемента управления, прежде чем он вызывает InitializeComponent
. Так как SharedDictionaryManager.SharedDictionary
является статическим свойством, ResourceDictionary создается только один раз. Поскольку словарь ресурсов был объединен до вызова InitializeComponent
, ресурсы доступны для элемента управления в его файле XAML.
public NumericUpDown()
{
this.Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary);
InitializeComponent();
}
Определение ресурсов на уровне темы
WPF позволяет создавать ресурсы для разных тем Windows. Как разработчик элемента управления, вы можете определить ресурс для определенной темы, чтобы изменить внешний вид элемента управления в зависимости от того, какая тема используется. Например, внешний вид Button в классическом Windows темы (тема по умолчанию для Windows 2000) отличается от Button в теме Windows Luna (тема по умолчанию для Windows XP) так как Button использует другое ControlTemplate для каждой темы.
Ресурсы, относящиеся к теме, хранятся в словаре ресурсов с заданным именем файла. Эти файлы должны находиться в папке с именем Themes
, которая является подпапкой папки, содержащей элемент управления. В следующей таблице перечислены файлы словаря ресурсов и темы, связанные с каждым файлом.
Имя файла словаря ресурсов | Тема Windows |
---|---|
Classic.xaml |
Классический вид Windows 9x/2000 для Windows XP |
Luna.NormalColor.xaml |
Синяя тема по умолчанию в Windows XP |
Luna.Homestead.xaml |
Оливковая тема в Windows XP |
Luna.Metallic.xaml |
Серебристая тема в Windows XP |
Royale.NormalColor.xaml |
Тема по умолчанию в Windows XP Media Center Edition |
Aero.NormalColor.xaml |
Тема по умолчанию в Windows Vista |
Не нужно определять ресурс для каждой темы. Если ресурс не определен для конкретной темы, элемент управления проверяет Classic.xaml
для ресурса. Если ресурс не определен в файле, соответствующем текущей теме, или в Classic.xaml
, то элемент управления использует общий ресурс, который находится в файле словаря ресурса с именем generic.xaml
. Файл generic.xaml
расположен в той же папке, что и файлы словаря ресурсов, связанные с темами. Хотя generic.xaml
не соответствует конкретной теме Windows, он по-прежнему является словарем уровня темы.
Пример пользовательского элемента управления NumericUpDown с темами и поддержкой автоматизации пользовательского интерфейса содержит два словаря ресурсов для элемента управления NumericUpDown
: один — в файле generic.xaml, а второй — в Luna.NormalColor.xaml. Можно запустить приложение и переключаться между серебристой темой в Windows XP и другой темой, чтобы увидеть разницу между двумя шаблонами элемента управления. (Если вы используете Windows Vista, можно переименовать Luna.NormalColor.xaml в Aero.NormalColor.xaml и переключаться между двумя темами, например между классической и темой по умолчанию для Windows Vista.)
При переводе ControlTemplate в любом из файлы словарей тематических ресурсов, необходимо создать статический конструктор для элемента управления и вызвать OverrideMetadata(Type, PropertyMetadata) метод DefaultStyleKey, как показано в следующем примере.
static NumericUpDown()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}
Определение и создание ссылок на ключи для ресурсов тем
При определении ресурса на уровне элемента можно назначить строку в качестве его ключа и получить доступ к ресурсу через эту строку. При определении ресурса на уровне темы необходимо использовать ComponentResourceKey как ключ. В следующем примере определяется ресурс в файле generic.xaml.
<LinearGradientBrush
x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:Painter},
ResourceId=MyEllipseBrush}"
StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="Blue" Offset="0" />
<GradientStop Color="Red" Offset="0.5" />
<GradientStop Color="Green" Offset="1"/>
</LinearGradientBrush>
Следующий пример ссылается на ресурс, указав ComponentResourceKey как ключ.
<RepeatButton
Grid.Column="1" Grid.Row="0"
Background="{StaticResource {ComponentResourceKey
TypeInTargetAssembly={x:Type local:NumericUpDown},
ResourceId=ButtonBrush}}">
Up
</RepeatButton>
<RepeatButton
Grid.Column="1" Grid.Row="1"
Background="{StaticResource {ComponentResourceKey
TypeInTargetAssembly={x:Type local:NumericUpDown},
ResourceId=ButtonBrush}}">
Down
</RepeatButton>
Определение местоположения ресурсов тем
Чтобы найти ресурсы для элемента управления, ведущее приложение должно знать, что сборка содержит ресурсы для элемента управления. Можно сделать, добавив ThemeInfoAttribute на сборку, содержащую элемент управления. ThemeInfoAttribute Имеет GenericDictionaryLocation свойство, указывающее местоположение общих ресурсов и ThemeDictionaryLocation свойство, указывающее расположение тематических ресурсов.
В следующем примере задается GenericDictionaryLocation и ThemeDictionaryLocation свойства SourceAssembly, чтобы указать, что универсальных и тематических ресурсы находятся в той же сборке, в качестве элемента управления.
[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly,
ResourceDictionaryLocation.SourceAssembly)]