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

Расширяемость модели элементов управления Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) значительно уменьшает необходимость создания новых элементов управления.The extensibility of the Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF) control model greatly reduces the need to create a new control. Однако в некоторых случаях может потребоваться создать пользовательский элемент управления.However, in certain cases you may still need to create a custom control. В этом разделе обсуждаются функции, которые уменьшают необходимость создания пользовательских элементов управления, а также различные модели создания элементов управления в Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF).This topic discusses the features that minimize your need to create a custom control and the different control authoring models in Windows Presentation Foundation (WPF)Windows Presentation Foundation (WPF). Также здесь демонстрируется создание нового элемента управления.This topic also demonstrates how to create a new control.

Альтернативы написанию нового элемента управленияAlternatives to Writing a New Control

Исторически сложилось, что если нужно настроить вид существующего элемента управления, то ограничиваются изменением его стандартных свойств, таких как цвет фона, ширина границы и размер шрифта.Historically, if you wanted to get a customized experience from an existing control, you were limited to changing the standard properties of the control, such as background color, border width, and font size. Если необходимо расширить внешний вид или поведение элемента управления за пределы этих предопределенных параметров, то необходимо создать новый элемент управления, как правило, путем наследования от существующего элемента управления и переопределения метода, ответственного за отрисовку элемента управления.If you wished to extend the appearance or behavior of a control beyond these predefined parameters, you would need to create a new control, usually by inheriting from an existing control and overriding the method responsible for drawing the control. Кроме того, WPFWPF позволяет настраивать существующие элементы управления с помощью модели форматированного содержимого, стилей, шаблонов и триггеров.Although that is still an option, WPFWPF enables to you customize existing controls by using its rich content model, styles, templates, and triggers. Ниже представлены примеры использования этих функций для создания настраиваемых и согласованных функциональных возможностей без необходимости создания нового элемента управления.The following list gives examples of how these features can be used to create custom and consistent experiences without having to create a new control.

  • Форматированное содержимое.Rich Content. Многие стандартные элементы управления WPFWPF поддерживают форматированное содержимое.Many of the standard WPFWPF controls support rich content. Например, свойство Content объекта Button имеет тип Object, поэтому теоретически все Buttonможет быть отображено в.For example, the content property of a Button is of type Object, so theoretically anything can be displayed on a Button. Чтобы кнопка TextBlock отображала изображение и текст, можно добавить изображение и StackPanel в Content и присвоить StackPanel свойству значение.To have a button display an image and text, you can add an image and a TextBlock to a StackPanel and assign the StackPanel to the Content property. Поскольку элементы управления могут отображать визуальные элементы WPFWPF и произвольные данные, это уменьшает необходимость создания нового элемента управления или изменения существующего для поддержки сложной визуализации.Because the controls can display WPFWPF visual elements and arbitrary data, there is less need to create a new control or to modify an existing control to support a complex visualization. Дополнительные сведения о модели содержимого для Button и других моделях содержимого в см. в WPFWPFразделе модель содержимого WPF.For more information about the content model for Button and other content models in WPFWPF, see WPF Content Model.

  • Стили.Styles. Style Представляет собой коллекцию значений, представляющих свойства элемента управления.A Style is a collection of values that represent properties for a control. С помощью стилей можно создать повторно используемое представление нужного внешнего вида и поведения элемента управления без написания нового элемента управления.By using styles, you can create a reusable representation of a desired control appearance and behavior without writing a new control. Например, предположим, что все TextBlock элементы управления должны иметь красный шрифт Arial с размером шрифта 14.For example, assume that you want all of your TextBlock controls to have red, Arial font with a font size of 14. Можно создать стиль как ресурс и задать соответствующие свойства.You can create a style as a resource and set the appropriate properties accordingly. Затем каждое TextBlock Добавление в приложение будет иметь одинаковый внешний вид.Then every TextBlock that you add to your application will have the same appearance.

  • Шаблоны данных.Data Templates. А DataTemplate позволяет настраивать способ отображения данных в элементе управления.A DataTemplate enables you to customize how data is displayed on a control. Например, DataTemplate можно использовать для указания способа отображения данных ListBoxв.For example, a DataTemplate can be used to specify how data is displayed in a ListBox. Пример см. в разделе Общие сведения о шаблонах данных.For an example of this, see Data Templating Overview. Помимо настройки внешнего вида данных, DataTemplate может включать элементы пользовательского интерфейса, что обеспечивает большую гибкость в пользовательских интерфейсах.In addition to customizing the appearance of data, a DataTemplate can include UI elements, which gives you a lot of flexibility in custom UIs. Например, с помощью DataTemplateможно ComboBox создать, в котором каждый элемент содержит флажок.For example, by using a DataTemplate, you can create a ComboBox in which each item contains a check box.

  • Шаблоны элементов управления.Control Templates. Многие элементы управления WPFWPF в ControlTemplate используют для определения структуры и внешнего вида элемента управления, которые разделяют внешний вид элемента управления от функциональных возможностей элемента управления.Many controls in WPFWPF use a ControlTemplate to define the control's structure and appearance, which separates the appearance of a control from the functionality of the control. Можно радикально изменить внешний вид элемента управления, переопределяя его ControlTemplate.You can drastically change the appearance of a control by redefining its ControlTemplate. Предположим, что нужен элемент управления, который выглядит как светофор.For example, suppose you want a control that looks like a stoplight. Этот элемент управления имеет простой пользовательский интерфейс и функциональные возможности.This control has a simple user interface and functionality. Элемент управления состоит из трех кругов, из которых одновременно загорается только один.The control is three circles, only one of which can be lit up at a time. После какого- RadioButton либо отражения можно подумать, что компонент предоставляет функции только одного выбранного за раз, но внешний вид RadioButton по умолчанию не выглядит ничего вроде огней на светофоре.After some reflection, you might realize that a RadioButton offers the functionality of only one being selected at a time, but the default appearance of the RadioButton looks nothing like the lights on a stoplight. Поскольку компонент использует шаблон элемента управления для определения его внешнего вида, легко ControlTemplate переопределить в соответствии с требованиями элемента управления и использовать переключатели для создания светофора. RadioButtonBecause the RadioButton uses a control template to define its appearance, it is easy to redefine the ControlTemplate to fit the requirements of the control, and use radio buttons to make your stoplight.

    Примечание

    Несмотря на то DataTemplate, что DataTemplate может использовать, в этом примере недостаточно. RadioButtonAlthough a RadioButton can use a DataTemplate, a DataTemplate is not sufficient in this example. DataTemplate Определяет внешний вид содержимого элемента управления.The DataTemplate defines the appearance of the content of a control. В случае RadioButton, содержимое отображается справа от окружности, которое указывает, выбран ли объект RadioButton .In the case of a RadioButton, the content is whatever appears to the right of the circle that indicates whether the RadioButton is selected. В примере светофора переключатель должен быть тем кругом, который может "загораться".In the example of the stoplight, the radio button needs just be a circle that can "light up." Так как требование внешнего вида для светофора отличается от внешнего вида RadioButtonпо умолчанию, необходимо ControlTemplateпереопределить.Because the appearance requirement for the stoplight is so different than the default appearance of the RadioButton, it is necessary to redefine the ControlTemplate. В целом, DataTemplate используется для определения содержимого (или данных) элемента управления, ControlTemplate а используется для определения структуры элемента управления.In general a DataTemplate is used for defining the content (or data) of a control, and a ControlTemplate is used for defining how a control is structured.

  • Триггеры.Triggers. Trigger Позволяет динамически изменять внешний вид и поведение элемента управления без создания нового элемента управления.A Trigger allows you to dynamically change the appearance and behavior of a control without creating a new control. Например, предположим, что в приложении ListBox есть несколько элементов управления и при их выборе требуется, ListBox чтобы элементы в каждом из них были полужирным и красным.For example, suppose you have multiple ListBox controls in your application and want the items in each ListBox to be bold and red when they are selected. Первым порывом может быть создание класса, который наследует от ListBox и OnSelectionChanged переопределяет метод для изменения внешнего вида выбранного элемента, но лучшим подходом является добавление триггера ListBoxItem к стилю, который изменяет внешний вид выбранный элемент.Your first instinct might be to create a class that inherits from ListBox and override the OnSelectionChanged method to change the appearance of the selected item, but a better approach is to add a trigger to a style of a ListBoxItem that changes the appearance of the selected item. Триггер позволяет изменять значения свойств или выполнять действия в зависимости от значения свойства.A trigger enables you to change property values or take actions based on the value of a property. EventTrigger Позволяет выполнять действия при возникновении события.An EventTrigger enables you to take actions when an event occurs.

Дополнительные сведения о стилях, шаблонах и триггерах см. в разделе Использование стилей и шаблонов.For more information about styles, templates, and triggers, see Styling and Templating.

В целом, если элемент управления отражает функциональность существующего элемента управления, но должен выглядеть по-другому, сначала следует рассмотреть возможность использования какого-либо из методов, описанных в этом разделе, для изменения внешнего вида существующего элемента.In general, if your control mirrors the functionality of an existing control, but you want the control to look different, you should first consider whether you can use any of the methods discussed in this section to change the existing control's appearance.

Модели для создания элементов управленияModels for Control Authoring

Модель форматированного содержимого, стили, шаблоны и триггеры уменьшают необходимость создания новых элементов управления.The rich content model, styles, templates, and triggers minimize the need for you to create a new control. Тем не менее, если необходимо создать новый элемент управления, важно понимать различные модели создания элементов управления в WPFWPF.However, if you do need to create a new control, it is important to understand the different control authoring models in WPFWPF. WPFWPF предоставляет три общих модели для создания элемента управления, которые имеют различный набор функций и уровень гибкости.provides three general models for creating a control, each of which provides a different set of features and level of flexibility. Базовыми классами для трех моделей являются UserControl, Controlи FrameworkElement.The base classes for the three models are UserControl, Control, and FrameworkElement.

Создание производных классов от UserControlDeriving from UserControl

Самый простой способ создать элемент управления в WPFWPF заключается в наследовании от. UserControlThe simplest way to create a control in WPFWPF is to derive from UserControl. При построении элемента управления, наследуемого UserControlот, добавляются существующие компоненты UserControl, имена компонентов и ссылки на обработчики событий в Язык XAMLExtensible Application Markup Language (XAML).When you build a control that inherits from UserControl, you add existing components to the UserControl, name the components, and reference event handlers in Язык XAMLExtensible Application Markup Language (XAML). Затем можно ссылаться на именованные элементы и определять обработчики событий в коде.You can then reference the named elements and define the event handlers in code. Эта модель разработки очень схожа с моделью, используемой для разработки приложений в WPFWPF.This development model is very similar to the model used for application development in WPFWPF.

При правильном UserControl построении может воспользоваться преимуществами расширенного содержимого, стилей и триггеров.If built correctly, a UserControl can take advantage of the benefits of rich content, styles, and triggers. Однако если элемент управления наследуется от UserControl, то пользователи, использующие ваш элемент управления, не смогут DataTemplate использовать или ControlTemplate для настройки внешнего вида.However, if your control inherits from UserControl, people who use your control will not be able to use a DataTemplate or ControlTemplate to customize its appearance. Для создания пользовательского элемента управления, поддерживающего Control шаблоны, необходимо получить от класса или одного из его UserControlпроизводных классов (кроме).It is necessary to derive from the Control class or one of its derived classes (other than UserControl) to create a custom control that supports templates.

Преимущества использования производного класса от UserControlBenefits of Deriving from UserControl

Рассмотрите возможность наследования от UserControl , если применяются все следующие условия.Consider deriving from UserControl if all of the following apply:

  • Нужно создать элемент управления аналогично созданию приложения.You want to build your control similarly to how you build an application.

  • Элемент управления состоит только из существующих компонентов.Your control consists only of existing components.

  • Не нужно поддерживать сложные настройки.You don't need to support complex customization.

Создание производного от элемента управленияDeriving from Control

Наследование от Control класса — это модель, используемая большинством существующих WPFWPF элементов управления.Deriving from the Control class is the model used by most of the existing WPFWPF controls. При создании элемента управления, который наследуется от Control класса, его внешний вид определяется с помощью шаблонов.When you create a control that inherits from the Control class, you define its appearance by using templates. Таким образом, можно отделить рабочую логику от визуального представления.By doing so, you separate the operational logic from the visual representation. Кроме того, можно обеспечить разделение пользовательского интерфейса и логики с помощью команд и привязок вместо событий и избегать ссылок на элементы в, когда ControlTemplate это возможно.You can also ensure the decoupling of the UI and logic by using commands and bindings instead of events and avoiding referencing elements in the ControlTemplate whenever possible. Если пользовательский интерфейс и логика элемента управления правильно отделены, пользователь элемента управления может переопределять внешний вид элемента управления ControlTemplate .If the UI and logic of your control are properly decoupled, a user of your control can redefine the control's ControlTemplate to customize its appearance. Хотя создание пользовательского Control не так просто UserControl, как создание, пользователь Control предоставляет наибольшую гибкость.Although building a custom Control is not as simple as building a UserControl, a custom Control provides the most flexibility.

Преимущества использования производного от элемента управленияBenefits of Deriving from Control

Рекомендуется использовать наследование Control вместо класса, UserControl если применяется одно из следующих условий:Consider deriving from Control instead of using the UserControl class if any of the following apply:

  • Необходимо настроить внешний вид элемента управления с помощью ControlTemplate.You want the appearance of your control to be customizable via the ControlTemplate.

  • Элемент управления должен поддерживать различные темы.You want your control to support different themes.

Создание производного от FrameworkElementDeriving from FrameworkElement

Элементы управления, производные UserControl от Control или основанные на компоновке существующих элементов.Controls that derive from UserControl or Control rely upon composing existing elements. Во многих сценариях это приемлемое решение, поскольку любой объект, наследующий от FrameworkElement , может находиться ControlTemplateв.For many scenarios, this is an acceptable solution, because any object that inherits from FrameworkElement can be in a ControlTemplate. Однако бывают случаи, когда для внешнего вида элемента управления требуется больше, чем функциональность простой композиции элементов.However, there are times when a control's appearance requires more than the functionality of simple element composition. В этих сценариях необходимо выбрать компонент на FrameworkElement основе правильного выбора.For these scenarios, basing a component on FrameworkElement is the right choice.

Существует два стандартных метода для создания FrameworkElementкомпонентов на основе: прямая отрисовка и настраиваемая композиция элементов.There are two standard methods for building FrameworkElement-based components: direct rendering and custom element composition. Прямая отрисовка включает переопределение OnRender FrameworkElement метода и предоставление DrawingContext операций, явно определяющих визуальные элементы компонента.Direct rendering involves overriding the OnRender method of FrameworkElement and providing DrawingContext operations that explicitly define the component visuals. Это метод, используемый Image и. BorderThis is the method used by Image and Border. Пользовательская композиция элементов включает использование объектов типа Visual для составления внешнего вида компонента.Custom element composition involves using objects of type Visual to compose the appearance of your component. Например, см. раздел Использование объектов DrawingVisual.For an example, see Using DrawingVisual Objects. Track— Это пример элемента управления в, WPFWPF в котором используется пользовательская композиция элементов.Track is an example of a control in WPFWPF that uses custom element composition. Можно также комбинировать прямую отрисовку и пользовательскую композицию элемента в одном элементе управления.It is also possible to mix direct rendering and custom element composition in the same control.

Преимущества использования производного от FrameworkElementBenefits of Deriving from FrameworkElement

Рассмотрите возможность наследования от FrameworkElement , если применяется одно из следующих условий:Consider deriving from FrameworkElement if any of the following apply:

  • Требуется точный контроль над внешним видом элемента управления помимо того, который обеспечивается простой композицией элемента.You want to have precise control over the appearance of your control beyond what is provided by simple element composition.

  • Необходимо определить внешний вид элемента управления путем определения собственной логики отрисовки.You want to define the appearance of your control by defining your own render logic.

  • Необходимо составить существующие элементы нестандартными UserControl способами, которые выходят за рамки возможностей и. ControlYou want to compose existing elements in novel ways that go beyond what is possible with UserControl and Control.

Основы создания элементов управленияControl Authoring Basics

Как обсуждалось выше, одной из наиболее мощных функций WPFWPF является возможность выхода за пределы задания базовых свойств элемента управления, чтобы изменить его внешний вид и поведение, но без необходимости создания пользовательского элемента управления.As discussed earlier, one of the most powerful features of WPFWPF is the ability to go beyond setting basic properties of a control to change its appearance and behavior, yet still not needing to create a custom control. Функции стилей, привязки данных и триггеров предоставляются системой свойств WPFWPF и системой событий WPFWPF.The styling, data binding, and trigger features are made possible by the WPFWPF property system and the WPFWPF event system. В следующих разделах описаны некоторые рекомендации, которым нужно следовать независимо от модели, используемой для создания пользовательского элемента управления. Это необходимо, чтобы пользователи вашего пользовательского элемента управления могли использовать эти функции точно так же, как для элемента управления, входящего в WPFWPF.The following sections describe some practices that you should follow, regardless of the model you use to create the custom control, so that users of your custom control can use these features just as they would for a control that is included with WPFWPF.

Использование свойств зависимостейUse Dependency Properties

Если свойство является свойством зависимостей, то можно сделать следующее:When a property is a dependency property, it is possible to do the following:

  • Установить свойство в стиле.Set the property in a style.

  • Привязать свойство к источнику данных.Bind the property to a data source.

  • Использовать динамический ресурс в качестве значения свойства.Use a dynamic resource as the property's value.

  • Анимировать свойство.Animate the property.

Если свойство элемента управления должно поддерживать подобную функциональность, то следует реализовать его как свойство зависимостей.If you want a property of your control to support any of this functionality, you should implement it as a dependency property. В следующем примере определяется свойство зависимостей с именем Value следующим способом:The following example defines a dependency property named Value by doing the following:

  • ValueProperty public Определите идентификатор с именем в видеstatic поля .readonly DependencyPropertyDefine a DependencyProperty identifier named ValueProperty as a public static readonly field.

  • Зарегистрируйте имя свойства в системе свойств, вызвав DependencyProperty.Registerметод, чтобы указать следующее:Register the property name with the property system, by calling DependencyProperty.Register, to specify the following:

    • Имя свойства.The name of the property.

    • Тип свойства.The type of the property.

    • Тип, к которому принадлежит это свойство.The type that owns the property.

    • Метаданные для свойства.The metadata for the property. Метаданные содержат значение свойства по умолчанию, CoerceValueCallback PropertyChangedCallbackи.The metadata contains the property's default value, a CoerceValueCallback and a PropertyChangedCallback.

  • Определите свойство оболочки CLR с именем Value, которое совпадает с именем, используемым для регистрации свойства зависимостей, путем реализации методов доступа get свойства и set .Define a CLR wrapper property named Value, which is the same name that is used to register the dependency property, by implementing the property's get and set accessors. Обратите внимание get , set что методы доступа и GetValue вызывают SetValue только метод и соответственно.Note that the get and set accessors only call GetValue and SetValue respectively. Рекомендуется, чтобы методы доступа свойств зависимостей не содержали дополнительной логики, так как WPFWPF клиенты и могут обходить методы доступа GetValue , SetValue а также вызывать и напрямую.It is recommended that the accessors of dependency properties not contain additional logic because clients and WPFWPF can bypass the accessors and call GetValue and SetValue directly. Например, если свойство привязано к источнику данных, то метод доступа set свойства не вызывается.For example, when a property is bound to a data source, the property's set accessor is not called. Вместо того чтобы добавлять дополнительную логику в методы доступа get и Set, используйте ValidateValueCallbackделегаты, PropertyChangedCallback CoerceValueCallbackи для ответа или проверки значения при его изменении.Instead of adding additional logic to the get and set accessors, use the ValidateValueCallback, CoerceValueCallback, and PropertyChangedCallback delegates to respond to or check the value when it changes. Дополнительные сведения об этих обратных вызовах см. в разделе Проверка и обратные вызовы свойства зависимостей.For more information on these callbacks, see Dependency Property Callbacks and Validation.

  • Определите метод для CoerceValueCallback именованного CoerceValueметода.Define a method for the CoerceValueCallback named CoerceValue. CoerceValue гарантирует, что Value больше или равно MinValue и меньше или равно MaxValue.CoerceValue ensures that Value is greater or equal to MinValue and less than or equal to MaxValue.

  • Определите метод для PropertyChangedCallbackс именем OnValueChanged.Define a method for the PropertyChangedCallback, named OnValueChanged. OnValueChangedСоздает объект и готовится к ValueChanged вызову перенаправленного события. RoutedPropertyChangedEventArgs<T>OnValueChanged creates a RoutedPropertyChangedEventArgs<T> object and prepares to raise the ValueChanged routed event. Перенаправляемые события рассматриваются в следующем разделе.Routed events are discussed in the next section.

       /// <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

Дополнительные сведения см. в разделе Пользовательские свойства зависимостей.For more information, see Custom Dependency Properties.

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

Так же как свойства зависимостей расширяют понятие свойств среды CLR с дополнительными функциональными возможностями, перенаправленные события расширяют понятие стандартных событий CLR.Just as dependency properties extend the notion of CLR properties with additional functionality, routed events extend the notion of standard CLR events. При создании нового элемента управления WPFWPF рекомендуется также реализовывать событие как перенаправляемое, так как такие события поддерживают следующее поведение:When you create a new WPFWPF control, it is also good practice to implement your event as a routed event because a routed event supports the following behavior:

  • События могут обрабатываться в родительском элементе нескольких элементов управления.Events can be handled on a parent of multiple controls. Если событие является событием восходящей маршрутизации, то один родительский элемент в дереве элементов может подписаться на это событие.If an event is a bubbling event, a single parent in the element tree can subscribe to the event. Разработчики приложений могут использовать один обработчик для реагирования на событие нескольких элементов управления.Then application authors can use one handler to respond to the event of multiple controls. Например, если элемент управления является частью каждого элемента в ListBox (так как он включен DataTemplateв), разработчик приложения может определить обработчик событий для события ListBoxэлемента управления в.For example, if your control is a part of each item in a ListBox (because it is included in a DataTemplate), the application developer can define the event handler for your control's event on the ListBox. Обработчик событий вызывается при возникновении события в любом элементе управления.Whenever the event occurs on any of the controls, the event handler is called.

  • Перенаправленные события могут использоваться в EventSetter, что позволяет разработчикам приложений указывать обработчик события в стиле.Routed events can be used in an EventSetter, which enables application developers to specify the handler of an event within a style.

  • Перенаправленные события можно использовать в EventTrigger, что удобно для анимации свойств с помощью. XAMLXAMLRouted events can be used in an EventTrigger, which is useful for animating properties by using XAMLXAML. Более подробную информацию см. в разделе Общие сведения об эффектах анимации.For more information, see Animation Overview.

Следующий пример определяет перенаправляемое событие:The following example defines a routed event by doing the following:

  • ValueChangedEvent public Определите идентификатор с именем в видеstatic поля .readonly RoutedEventDefine a RoutedEvent identifier named ValueChangedEvent as a public static readonly field.

  • Зарегистрируйте перенаправленное событие, вызвав EventManager.RegisterRoutedEvent метод.Register the routed event by calling the EventManager.RegisterRoutedEvent method. В примере при вызове метода RegisterRoutedEventуказываются следующие сведения:The example specifies the following information when it calls RegisterRoutedEvent:

    • Имя события ValueChanged.The name of the event is ValueChanged.

    • Стратегия маршрутизации — Bubbleэто, что означает, что обработчик событий в источнике (объект, вызывающий событие) вызывается первым, а затем обработчики событий в родительских элементах источника вызываются в случае успеха, начиная с обработчика событий в ближайшем родительский элемент.The routing strategy is Bubble, which means that an event handler on the source (the object that raises the event) is called first, and then event handlers on the source's parent elements are called in succession, starting with the event handler on the closest parent element.

    • Тип обработчика событий создается RoutedPropertyChangedEventHandler<T> Decimal с помощью типа.The type of the event handler is RoutedPropertyChangedEventHandler<T>, constructed with a Decimal type.

    • Тип — владелец события — NumericUpDown.The owning type of the event is NumericUpDown.

  • Объявите общее событие с именем ValueChanged, которое включает объявления метода доступа к событию.Declare a public event named ValueChanged and includes event-accessor declarations. В примере AddHandler вызывается remove add в объявлении метода RemoveHandler доступа и в объявлении метода доступа WPFWPF для использования служб событий.The example calls AddHandler in the add accessor declaration and RemoveHandler in the remove accessor declaration to use the WPFWPF event services.

  • Создайте защищенный виртуальный метод с именем OnValueChanged, вызывающий событие ValueChanged.Create a protected, virtual method named OnValueChanged that raises the ValueChanged event.

/// <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

Дополнительные сведения см. в разделах Общие сведения о перенаправляемых событиях и Создание пользовательских перенаправляемых событий.For more information, see Routed Events Overview and Create a Custom Routed Event.

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

Для отделения пользовательского интерфейса от логики элемента управления можно использовать привязку данных.To decouple the UI of your control from its logic, consider using data binding. Это особенно важно, если вы определяете внешний вид элемента управления с помощью ControlTemplate.This is particularly important if you define the appearance of your control by using a ControlTemplate. При использовании привязки данных можно избавиться от необходимости ссылаться на определенные части пользовательского интерфейса из кода.When you use data binding, you might be able to eliminate the need to reference specific parts of the UI from the code. Рекомендуется избегать ссылок ControlTemplate на элементы, находящихся в, так как когда код ссылается на элементы, находящихся ControlTemplate в, и ControlTemplate изменяется, элемент, на который указывает ссылка, должен быть включен в ControlTemplateновый.It's a good idea to avoid referencing elements that are in the ControlTemplate because when the code references elements that are in the ControlTemplate and the ControlTemplate is changed, the referenced element needs to be included in the new ControlTemplate.

В следующем примере выполняется обновление TextBlock NumericUpDown элемента управления, присвоение ему имени и ссылки на текстовое поле по имени в коде.The following example updates the TextBlock of the NumericUpDown control, assigning a name to it and referencing the textbox by name in code.

<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

Следующий пример использует привязку для достижения такого же результата.The following example uses binding to accomplish the same thing.

<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>

Дополнительные сведения о привязке данных см. в разделе Общие сведения о привязке данных.For more information about data binding, see Data Binding Overview.

Разработка для конструкторовDesign for Designers

Чтобы получить поддержку пользовательских элементов управления WPF в Конструктор WPF для Visual StudioWPF Designer for Visual Studio (например, редактирование свойства с помощью окна "Свойства"), следуйте приведенным ниже рекомендациям.To receive support for custom WPF controls in the Конструктор WPF для Visual StudioWPF Designer for Visual Studio (for example, property editing with the Properties window), follow these guidelines. Дополнительные сведения о разработке для Конструктор WPFWPF Designerсм. в разделе Разработка XAML в Visual Studio.For more information on developing for the Конструктор WPFWPF Designer, see Design XAML in Visual Studio.

Свойства зависимостейDependency Properties

Не забудьте реализовать CLR get и set методы доступа, как описано выше в разделе "использование свойств зависимостей".Be sure to implement CLR get and set accessors as described earlier, in "Use Dependency Properties." Конструкторы могут использовать программы-оболочки для обнаружения свойства зависимостей, но им, как WPFWPF и клиентам элемента управления, не требуется вызывать методы доступа при получении или настройке свойства.Designers may use the wrapper to detect the presence of a dependency property, but they, like WPFWPF and clients of the control, are not required to call the accessors when getting or setting the property.

Вложенные свойстваAttached Properties

При реализации вложенных свойств в пользовательских элементах управления учитывайте следующие рекомендации:You should implement attached properties on custom controls using the following guidelines:

  • public ИмеетформуProperty PropertyName , созданную с помощью методаRegisterAttached . static readonly DependencyPropertyHave a public static readonly DependencyProperty of the form PropertyNameProperty that was creating using the RegisterAttached method. Имя свойства, которое передается RegisterAttached в, должно соответствовать PropertyName.The property name that is passed to RegisterAttached must match PropertyName.

  • Реализуйте пару методов CLR public static с именем SetPropertyName и GetPropertyName.Implement a pair of public static CLR methods named SetPropertyName and GetPropertyName. Оба метода должны принимать класс, производный DependencyProperty от, в качестве первого аргумента.Both methods should accept a class derived from DependencyProperty as their first argument. Метод SetPropertyName также принимает аргумент, тип которого соответствует зарегистрированному типу данных для свойства.The SetPropertyName method also accepts an argument whose type matches the registered data type for the property. Метод GetPropertyName должен возвращать значение такого же типа.The GetPropertyName method should return a value of the same type. Если метод Set PropertyName отсутствует, свойство отмечается как "только для чтения".If the SetPropertyName method is missing, the property is marked read-only.

  • SetPropertyName и Get PropertyName должны прямо маршрутизироваться к GetValue методам и SetValue в целевом объекте зависимости соответственно.Set PropertyName and GetPropertyName must route directly to the GetValue and SetValue methods on the target dependency object, respectively. Разработчики могут получить доступ к вложенному свойству, вызвав программу-оболочку метода или с помощью прямого вызова целевого объекта зависимостей.Designers may access the attached property by calling through the method wrapper or making a direct call to the target dependency object.

Дополнительные сведения о вложенных свойствах см. в разделе Общие сведения о вложенных свойствах.For more information on attached properties, see Attached Properties Overview.

Определение и использование общих ресурсовDefine and Use Shared Resources

Можно включить элемент управления в ту же сборку, что и приложение, или упаковать его в отдельную сборку, которая может использоваться в нескольких приложениях.You can include your control in the same assembly as your application, or you can package your control in a separate assembly that can be used in multiple applications. В большинстве случаев сведения, рассматриваемые в данном разделе, применяются независимо от используемого метода.For the most part, the information discussed in this topic applies regardless of the method you use. Однако есть одно отличие, о котором следует упомянуть.There is one difference worth noting, however. При помещении элемента управления в ту же сборку, что и приложение, можно добавить глобальные ресурсы в файл App.xaml.When you put a control in the same assembly as an application, you are free to add global resources to the App.xaml file. Но сборка, содержащая только элементы управления, не имеет Application связанного с ней объекта, поэтому файл App. XAML недоступен.But an assembly that contains only controls does not have an Application object associated with it, so an App.xaml file is not available.

Приложение выполняет поиск ресурса на трех уровнях в следующем порядке:When an application looks for a resource, it looks at three levels in the following order:

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

    Система начинает с элемента, который ссылается на ресурс, а затем ищет ресурсы логического родительского элемента и так далее, пока не будет достигнут корневой элемент.The system starts with the element that references the resource and then searches resources of the logical parent and so forth until the root element is reached.

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

    Ресурсы, определяемые Application объектом.Resources defined by the Application object.

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

    Словари уровня темы хранятся в подпапке "Темы".Theme-level dictionaries are stored in a subfolder named Themes. Файлы в папке "Темы" соответствуют темам.The files in the Themes folder correspond to themes. Например, могут присутствовать файлы Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml и т. д.For example, you might have Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml, and so on. Также может присутствовать файл с именем generic.xaml.You can also have a file named generic.xaml. Когда система ищет ресурс на уровне темы, она сначала ищет его в файле конкретной темы, а затем в файле generic.xaml.When the system looks for a resource at the themes level, it first looks for it in the theme-specific file and then looks for it in generic.xaml.

Если элемент управления находится в сборке отдельно от приложения, глобальные ресурсы необходимо поместить на уровень элемента или на уровень темы.When your control is in an assembly that is separate from the application, you must put your global resources at the element level or at the theme level. Оба метода имеют свои преимущества.Both methods have their advantages.

Определение ресурсов на уровне элементаDefining Resources at the Element Level

Общие ресурсы на уровне элемента можно определить путем создания пользовательского словаря ресурсов и его объединения со словарем ресурсов элемента управления.You can define shared resources at the element level by creating a custom resource dictionary and merging it with your control’s resource dictionary. При использовании этого метода можно присвоить файлу ресурсов любое имя и его можно поместить в одну папку с элементами управления.When you use this method, you can name your resource file anything you want, and it can be in the same folder as your controls. Ресурсы на уровне элемента также могут использовать простые строки как ключи.Resources at the element level can also use simple strings as keys. В следующем примере создается LinearGradientBrush файл ресурсов с именем Dictionary1. XAML.The following example creates a LinearGradientBrush resource file named 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>

После определения словаря необходимо его объединить со словарем ресурсов элемента управления.Once you have defined your dictionary, you need to merge it with your control's resource dictionary. Это можно сделать с помощью XAMLXAML или кода.You can do this by using XAMLXAML or code.

Следующий пример объединяет словарь ресурса с помощью XAMLXAML.The following example merges a resource dictionary by using XAMLXAML.

<UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Dictionary1.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</UserControl.Resources>

Недостатком этого подхода является то, что ResourceDictionary объект создается каждый раз при ссылке на него.The disadvantage to this approach is that a ResourceDictionary object is created each time you reference it. Например, если в библиотеке имеется 10 пользовательских элементов управления и объединены словари общих ресурсов для каждого элемента управления с помощью XAML, то создается 10 идентичных ResourceDictionary объектов.For example, if you have 10 custom controls in your library and merge the shared resource dictionaries for each control by using XAML, you create 10 identical ResourceDictionary objects. Это можно избежать, создав статический класс, который объединяет ресурсы в коде и возвращает полученный результат ResourceDictionary.You can avoid this by creating a static class that merges the resources in code and returns the resulting ResourceDictionary.

В следующем примере создается класс, который возвращает общий ResourceDictionaryобъект.The following example creates a class that returns a shared 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.The following example merges the shared resource with the resources of a custom control in the control's constructor before it calls InitializeComponent. Поскольку является статическим свойством, объект ResourceDictionary создается только один раз. SharedDictionaryManager.SharedDictionaryBecause the SharedDictionaryManager.SharedDictionary is a static property, the ResourceDictionary is created only once. Поскольку словарь ресурсов был объединен до вызова InitializeComponent, ресурсы доступны для элемента управления в его файле XAMLXAML.Because the resource dictionary was merged before InitializeComponent was called, the resources are available to the control in its XAMLXAML file.

public NumericUpDown()
{
    this.Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary);
    InitializeComponent();

}

Определение ресурсов на уровне темыDefining Resources at the Theme Level

WPFWPF позволяет создавать ресурсы для разных тем Windows.enables you to create resources for different Windows themes. Как разработчик элемента управления, вы можете определить ресурс для определенной темы, чтобы изменить внешний вид элемента управления в зависимости от того, какая тема используется.As a control author, you can define a resource for a specific theme to change your control's appearance depending on what theme is in use. Button Например, внешний вид в классической теме Windows (тема по умолчанию для Windows 2000) отличается от элемента Button в теме Windows Luna (тема по умолчанию для Button Windows XP), так как использует другой ControlTemplate для каждой темы.For example, the appearance of a Button in the Windows Classic theme (the default theme for Windows 2000) differs from a Button in the Windows Luna theme (the default theme for Windows XP) because the Button uses a different ControlTemplate for each theme.

Ресурсы, относящиеся к теме, хранятся в словаре ресурсов с заданным именем файла.Resources that are specific to a theme are kept in a resource dictionary with a specific file name. Эти файлы должны находиться в папке с именем Themes, которая является подпапкой папки, содержащей элемент управления.These files must be in a folder named Themes that is a subfolder of the folder that contains the control. В следующей таблице перечислены файлы словаря ресурсов и темы, связанные с каждым файлом.The following table lists the resource dictionary files and the theme that is associated with each file:

Имя файла словаря ресурсовResource dictionary file name Тема WindowsWindows theme
Classic.xaml Классический вид Windows 9x/2000 для Windows XPClassic Windows 9x/2000 look on Windows XP
Luna.NormalColor.xaml Синяя тема по умолчанию в Windows XPDefault blue theme on Windows XP
Luna.Homestead.xaml Оливковая тема в Windows XPOlive theme on Windows XP
Luna.Metallic.xaml Серебристая тема в Windows XPSilver theme on Windows XP
Royale.NormalColor.xaml Тема по умолчанию в Windows XP Media Center EditionDefault theme on Windows XP Media Center Edition
Aero.NormalColor.xaml Тема по умолчанию в Windows VistaDefault theme on Windows Vista

Не нужно определять ресурс для каждой темы.You do not need to define a resource for every theme. Если ресурс не определен для конкретной темы, элемент управления проверяет Classic.xaml для ресурса.If a resource is not defined for a specific theme, then the control checks Classic.xaml for the resource. Если ресурс не определен в файле, соответствующем текущей теме, или в Classic.xaml, то элемент управления использует общий ресурс, который находится в файле словаря ресурса с именем generic.xaml.If the resource is not defined in the file that corresponds to the current theme or in Classic.xaml, the control uses the generic resource, which is in a resource dictionary file named generic.xaml. Файл generic.xaml расположен в той же папке, что и файлы словаря ресурсов, связанные с темами.The generic.xaml file is located in the same folder as the theme-specific resource dictionary files. Хотя generic.xaml не соответствует конкретной теме Windows, он по-прежнему является словарем уровня темы.Although generic.xaml does not correspond to a specific Windows theme, it is still a theme-level dictionary.

Пример пользовательского элемента управления NumericUpDown с темами и поддержкой автоматизации пользовательского интерфейса содержит два словаря ресурсов для элемента управления NumericUpDown: один — в файле generic.xaml, а второй — в Luna.NormalColor.xaml.NumericUpDown Custom Control with Theme and UI Automation Support Sample contains two resource dictionaries for the NumericUpDown control: one is in generic.xaml and one is in Luna.NormalColor.xaml. Можно запустить приложение и переключаться между серебристой темой в Windows XP и другой темой, чтобы увидеть разницу между двумя шаблонами элемента управления.You can run the application and switch between the Silver theme in Windows XP and another theme to see the difference between the two control templates. (Если вы используете Windows Vista, можно переименовать Luna.NormalColor.xaml в Aero.NormalColor.xaml и переключаться между двумя темами, например между классической и темой по умолчанию для Windows Vista.)(If you are running Windows Vista, you can rename Luna.NormalColor.xaml to Aero.NormalColor.xaml and switch between two themes, such as Windows Classic and the default theme for Windows Vista.)

При помещении ControlTemplate в любой из файлов словаря ресурсов для конкретной темы необходимо создать статический конструктор для элемента управления и OverrideMetadata(Type, PropertyMetadata) вызвать метод в DefaultStyleKey, как показано в следующем примере.When you put a ControlTemplate in any of the theme-specific resource dictionary files, you must create a static constructor for your control and call the OverrideMetadata(Type, PropertyMetadata) method on the DefaultStyleKey, as shown in the following example.

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

При определении ресурса на уровне элемента можно назначить строку в качестве его ключа и получить доступ к ресурсу через эту строку.When you define a resource at the element level, you can assign a string as its key and access the resource via the string. При определении ресурса на уровне темы необходимо использовать в ComponentResourceKey качестве ключа.When you define a resource at the theme level, you must use a ComponentResourceKey as the key. В следующем примере определяется ресурс в файле generic.xaml.The following example defines a resource in 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 качестве ключа.The following example references the resource by specifying the ComponentResourceKey as the key.

<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>
Определение местоположения ресурсов темSpecifying the Location of Theme Resources

Чтобы найти ресурсы для элемента управления, ведущее приложение должно знать, что сборка содержит ресурсы для элемента управления.To find the resources for a control, the hosting application needs to know that the assembly contains control-specific resources. Это можно сделать, добавив ThemeInfoAttribute в сборку, содержащую элемент управления.You can accomplish that by adding the ThemeInfoAttribute to the assembly that contains the control. Имеет свойство, указывающее расположение ThemeDictionaryLocation универсальных ресурсов и свойство, которое указывает расположение ресурсов, зависящих от темы. GenericDictionaryLocation ThemeInfoAttributeThe ThemeInfoAttribute has a GenericDictionaryLocation property that specifies the location of generic resources, and a ThemeDictionaryLocation property that specifies the location of the theme-specific resources.

В следующем примере GenericDictionaryLocation свойству SourceAssemblyи ThemeDictionaryLocation присваивается значение, чтобы указать, что универсальные и зависящие от темы ресурсы находятся в той же сборке, что и элемент управления.The following example sets the GenericDictionaryLocation and ThemeDictionaryLocation properties to SourceAssembly, to specify that the generic and theme-specific resources are in the same assembly as the control.

[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, 
           ResourceDictionaryLocation.SourceAssembly)]
<Assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, ResourceDictionaryLocation.SourceAssembly)>

См. такжеSee also