Общие сведения о разработке элементов управления

Расширяемость модели элементов управления Windows Presentation Foundation (WPF) значительно уменьшает необходимость создания новых элементов управления. Однако в некоторых случаях может потребоваться создать пользовательский элемент управления. В этом разделе обсуждаются функции, которые уменьшают необходимость создания пользовательских элементов управления, а также различные модели создания элементов управления в Windows Presentation Foundation (WPF). Также здесь демонстрируется создание нового элемента управления.

Альтернативы написанию нового элемента управления

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

  • Форматированное содержимое. Многие стандартные элементы управления WPF поддерживают форматированное содержимое. Например, свойство Content объекта 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 соответствии с требованиями элемента управления и использовать переключатели для создания светофора.

    Примечание

    Несмотря на то 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 свойства не вызывается. Вместо того чтобы добавлять дополнительную логику в методы доступа get и 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);
}
''' <summary>
''' Identifies the Value dependency property.
''' </summary>
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Decimal), GetType(NumericUpDown), New FrameworkPropertyMetadata(MinValue, New PropertyChangedCallback(AddressOf OnValueChanged), New CoerceValueCallback(AddressOf CoerceValue)))

''' <summary>
''' Gets or sets the value assigned to the control.
''' </summary>
Public Property Value() As Decimal
    Get
        Return CDec(GetValue(ValueProperty))
    End Get
    Set(ByVal value As Decimal)
        SetValue(ValueProperty, value)
    End Set
End Property

Private Shared Overloads Function CoerceValue(ByVal element As DependencyObject, ByVal value As Object) As Object
    Dim newValue As Decimal = CDec(value)
    Dim control As NumericUpDown = CType(element, NumericUpDown)

    newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue))

    Return newValue
End Function

Private Shared Sub OnValueChanged(ByVal obj As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
    Dim control As NumericUpDown = CType(obj, NumericUpDown)

    Dim e As New RoutedPropertyChangedEventArgs(Of Decimal)(CDec(args.OldValue), CDec(args.NewValue), ValueChangedEvent)
    control.OnValueChanged(e)
End Sub

Дополнительные сведения см. в разделе Пользовательские свойства зависимостей.

Использование перенаправляемых событий

Так же как свойства зависимостей расширяют понятие свойств среды 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);
}
''' <summary>
''' Identifies the ValueChanged routed event.
''' </summary>
Public Shared ReadOnly ValueChangedEvent As RoutedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, GetType(RoutedPropertyChangedEventHandler(Of Decimal)), GetType(NumericUpDown))

''' <summary>
''' Occurs when the Value property changes.
''' </summary>
Public Custom Event ValueChanged As RoutedPropertyChangedEventHandler(Of Decimal)
    AddHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
        MyBase.AddHandler(ValueChangedEvent, value)
    End AddHandler
    RemoveHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
        MyBase.RemoveHandler(ValueChangedEvent, value)
    End RemoveHandler
    RaiseEvent(ByVal sender As System.Object, ByVal e As RoutedPropertyChangedEventArgs(Of Decimal))
    End RaiseEvent
End Event

''' <summary>
''' Raises the ValueChanged event.
''' </summary>
''' <param name="args">Arguments associated with the ValueChanged event.</param>
Protected Overridable Sub OnValueChanged(ByVal args As RoutedPropertyChangedEventArgs(Of Decimal))
    MyBase.RaiseEvent(args)
End Sub

Дополнительные сведения см. в разделах Общие сведения о перенаправляемых событиях и Создание пользовательских перенаправляемых событий.

Использование привязки

Для отделения пользовательского интерфейса от логики элемента управления можно использовать привязку данных. Это особенно важно, если вы определяете внешний вид элемента управления с помощью 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();
}
Private Sub UpdateTextBlock()
    valueText.Text = Value.ToString()
End Sub

Следующий пример использует привязку для достижения такого же результата.

<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 форму PropertyName Property , созданную с помощью RegisterAttached метода. Имя свойства, которое передается в, RegisterAttached должно соответствовать PropertyName.

  • Реализуйте пару методов CLR public static с именем SetPropertyName и GetPropertyName. Оба метода должны принимать класс, производный от DependencyProperty , в качестве первого аргумента. Метод SetPropertyName также принимает аргумент, тип которого соответствует зарегистрированному типу данных для свойства. Метод GetPropertyName должен возвращать значение такого же типа. Если метод SetPropertyName отсутствует, свойство отмечается как "только для чтения".

  • SetPropertyName и Get PropertyName должны прямо маршрутизироваться к GetValue SetValue методам и в целевом объекте зависимости соответственно. Разработчики могут получить доступ к вложенному свойству, вызвав программу-оболочку метода или с помощью прямого вызова целевого объекта зависимостей.

Дополнительные сведения о вложенных свойствах см. в разделе Общие сведения о вложенных свойствах.

Определение и использование общих ресурсов

Можно включить элемент управления в ту же сборку, что и приложение, или упаковать его в отдельную сборку, которая может использоваться в нескольких приложениях. В большинстве случаев сведения, рассматриваемые в данном разделе, применяются независимо от используемого метода. Однако есть одно отличие, о котором следует упомянуть. При помещении элемента управления в ту же сборку, что и приложение, можно добавить глобальные ресурсы в файл App.xaml. Но сборка, содержащая только элементы управления, не имеет Application связанного с ней объекта, поэтому файл App. XAML недоступен.

Приложение выполняет поиск ресурса на трех уровнях в следующем порядке:

  1. Уровень элемента.

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

  2. Уровень приложения.

    Ресурсы, определяемые Application объектом.

  3. Уровень темы.

    Словари уровня темы хранятся в подпапке "Темы". Файлы в папке "Темы" соответствуют темам. Например, могут присутствовать файлы 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, он по-прежнему является словарем уровня темы.

пример пользовательского элемента управления C# или Visual Basic NumericUpDown с темой и автоматизацией пользовательского интерфейса содержит два словаря ресурсов для NumericUpDown элемента управления: один — в generic. xaml, а другой — в Luna. NormalColor. xaml.

При помещении ControlTemplate в любой из файлов словаря ресурсов для конкретной темы необходимо создать статический конструктор для элемента управления и вызвать OverrideMetadata(Type, PropertyMetadata) метод в DefaultStyleKey , как показано в следующем примере.

static NumericUpDown()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
               new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}
Shared Sub New()
    DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
End Sub
Определение и создание ссылок на ключи для ресурсов тем

При определении ресурса на уровне элемента можно назначить строку в качестве его ключа и получить доступ к ресурсу через эту строку. При определении ресурса на уровне темы необходимо использовать в 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)]
<Assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, ResourceDictionaryLocation.SourceAssembly)>

См. также раздел