Control Authoring Overview
The extensibility of the 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. 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). This topic also demonstrates how to create a new control.
This topic contains the following sections.
- Alternatives to Writing a New Control
- Models for Control Authoring
- Control Authoring Basics
- Inheriting from UserControl vs. Using a ControlTemplate
- Related Topics
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. Although that is still an option, WPF 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. Many of the standard WPF controls support rich content. For example, the content property of a Button is of type Object, so theoretically anything can be displayed on a Button. 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. Because the controls can display WPF 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. For more information about the content model for Button and other controls, see Controls Content Model Overview. For more information about other content models in WPF, see Content Models.
Styles. 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. 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. Then every TextBlock that you add to your application will have the same appearance.
Data Templates. A DataTemplate enables you to customize how data is displayed on a control. 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. In addition to customizing the appearance of data, a DataTemplate can include UI elements, which gives you a lot of flexibility in custom UIs. For example, by using a DataTemplate, you can create a ComboBox in which each item contains a check box.
Control Templates. Many controls in WPF use a ControlTemplate to define the control's structure and appearance, which separates the appearance of a control from the functionality of the control. 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. 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. Because 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.
Note
Although a RadioButton can use a DataTemplate, a DataTemplate is not sufficient in this example. The DataTemplate defines the appearance of the content of a control. 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." Because the appearance requirement for the stoplight is so different than the default appearance of the RadioButton, it is necessary to redefine the 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. A Trigger allows you to dynamically change the appearance and behavior of a control without creating a new control. 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. 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. 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. However, if you do need to create a new control, it is important to understand the different control authoring models in WPF. WPF provides three general models for creating a control, each of which provides a different set of features and level of flexibility. The base classes for the three models are UserControl, Control, and FrameworkElement.
Deriving from UserControl
The simplest way to create a control in WPF is to derive from UserControl. When you build a control that inherits from UserControl, you add existing components to the UserControl, name the components, and reference event handlers in Extensible Application Markup Language (XAML). You can then reference the named elements and define the event handlers in code. This development model is very similar to the model used for application development in WPF.
If built correctly, a UserControl can take advantage of the benefits of rich content, styles, and triggers. 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. 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.
Benefits of Deriving from 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
Deriving from the Control class is the model used by most of the existing WPF controls. 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. 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. 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. 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
Consider deriving from Control instead of using the UserControl class if any of the following apply:
You want the appearance of your control to be customizable via the ControlTemplate.
You want your control to support different themes.
Deriving from FrameworkElement
Controls that derive from UserControl or Control rely upon composing existing elements. 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. For these scenarios, basing a component on FrameworkElement is the right choice.
There are two standard methods for building FrameworkElement-based components: direct rendering and custom element composition. Direct rendering involves overriding the OnRender method of FrameworkElement and providing DrawingContext operations that explicitly define the component visuals. This is the method used by Image and Border. Custom element composition involves using objects of type Visual to compose the appearance of your component. For an example, see Using DrawingVisual Objects. Track is an example of a control in WPF that uses custom element composition. It is also possible to mix direct rendering and custom element composition in the same control.
Benefits of Deriving from 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.
You want to compose existing elements in novel ways that go beyond what is possible with UserControl and Control.
Control Authoring Basics
As discussed earlier, one of the most powerful features of WPF 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. The styling, data binding, and trigger features are made possible by the WPF property system and the WPF event system. If you implement dependency properties and routed events in your control, users of your custom control can use these features just as they would for a control that ships with WPF, regardless of the model you use to create the custom control.
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. The following example defines a dependency property called Value by doing the following:
Define a DependencyProperty identifier called ValueProperty as a public static readonly field.
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. The metadata contains the property's default value, a CoerceValueCallback and a PropertyChangedCallback.
Define a CLR "wrapper" property called Value, which is the same name that is used to register the dependency property, by implementing the property's get and set accessors. Note that the get and set accessors only call GetValue and SetValue respectively. It is recommended that the accessors of dependency properties not contain additional logic because clients and WPF can bypass the accessors and call GetValue and SetValue directly. For example, when a property is bound to a data source, the property's set accessor is not called. 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.
Define a method for the CoerceValueCallback called CoerceValue. CoerceValue ensures that Value is greater or equal to MinValue and less than or equal to MaxValue.
Define a method for the PropertyChangedCallback, called OnValueChanged. 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);
}
For more information, see Custom Dependency Properties.
Use Routed Events
Just as dependency properties extend the notion of CLR properties with additional functionality, routed events extend the notion of standard CLR events. When you create a new WPF 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. 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.
Routed events can be used in an EventSetter, which enables application developers to specify the handler of an event within a style.
Routed events can be used in an EventTrigger, which is useful for animating properties by using XAML. For more information, see Animation Overview.
The following example defines a routed event by doing the following:
Define a RoutedEvent identifier called ValueChangedEvent as a public static readonly field.
Register the routed event by calling the EventManager.RegisterRoutedEvent method. The example specifies the following information when it calls RegisterRoutedEvent:
The name of the event is ValueChanged.
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.
The type of the event handler is RoutedPropertyChangedEventHandler<T>, constructed with a Decimal type.
The owning type of the event is NumericUpDown.
Declare a public event called ValueChanged and includes event-accessor declarations. The example calls AddHandler in the add accessor declaration and RemoveHandler in the remove accessor declaration to use the WPF event services.
Create a protected, virtual method called 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);
}
For more information, see Routed Events Overview and How to: Create a Custom Routed Event.
Use Binding
To decouple the UI of your control from its logic, consider using data binding. 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. 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.
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();
}
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.
Define and Use Commands
Consider defining and using commands instead of handling events to provide functionality. When you use event handlers in your control, the action done within the event handler is inaccessible to applications. When you implement commands on your control, applications can access the functionality that would otherwise be unavailable.
The following example is part of a control that handles the click event for two buttons to change the value of the NumericUpDown control. Regardless of whether the control is a UserControl or a Control with a ControlTemplate, the UI and logic are tightly coupled because the control uses event handlers.
<RepeatButton Name="upButton" Click="upButton_Click"
Grid.Column="1" Grid.Row="0">Up</RepeatButton>
<RepeatButton Name="downButton" Click="downButton_Click"
Grid.Column="1" Grid.Row="1">Down</RepeatButton>
private void upButton_Click(object sender, EventArgs e)
{
Value++;
}
private void downButton_Click(object sender, EventArgs e)
{
Value--;
}
The following example defines two commands that change the value of the NumericUpDown control.
public static RoutedCommand IncreaseCommand
{
get
{
return _increaseCommand;
}
}
public static RoutedCommand DecreaseCommand
{
get
{
return _decreaseCommand;
}
}
private static void InitializeCommands()
{
_increaseCommand = new RoutedCommand("IncreaseCommand", typeof(NumericUpDown));
CommandManager.RegisterClassCommandBinding(typeof(NumericUpDown),
new CommandBinding(_increaseCommand, OnIncreaseCommand));
CommandManager.RegisterClassInputBinding(typeof(NumericUpDown),
new InputBinding(_increaseCommand, new KeyGesture(Key.Up)));
_decreaseCommand = new RoutedCommand("DecreaseCommand", typeof(NumericUpDown));
CommandManager.RegisterClassCommandBinding(typeof(NumericUpDown),
new CommandBinding(_decreaseCommand, OnDecreaseCommand));
CommandManager.RegisterClassInputBinding(typeof(NumericUpDown),
new InputBinding(_decreaseCommand, new KeyGesture(Key.Down)));
}
private static void OnIncreaseCommand(object sender, ExecutedRoutedEventArgs e)
{
NumericUpDown control = sender as NumericUpDown;
if (control != null)
{
control.OnIncrease();
}
}
private static void OnDecreaseCommand(object sender, ExecutedRoutedEventArgs e)
{
NumericUpDown control = sender as NumericUpDown;
if (control != null)
{
control.OnDecrease();
}
}
protected virtual void OnIncrease()
{
Value++;
}
protected virtual void OnDecrease()
{
Value--;
}
private static RoutedCommand _increaseCommand;
private static RoutedCommand _decreaseCommand;
The elements in the template can then reference the commands, as shown in the following example.
<RepeatButton
Command="{x:Static local:NumericUpDown.IncreaseCommand}"
Grid.Column="1" Grid.Row="0">Up</RepeatButton>
<RepeatButton
Command="{x:Static local:NumericUpDown.DecreaseCommand}"
Grid.Column="1" Grid.Row="1">Down</RepeatButton>
Now applications can reference the bindings to access the functionality that was inaccessible when the control used event handlers. For more information about commands, see Commanding Overview.
Specifying That an Element Is Required in a ControlTemplate
The previous sections explain how to use data binding and commands so that a control does not reference elements in its ControlTemplate from code. However, there might be times when referencing an element is unavoidable. If this situation occurs, you should apply the TemplatePartAttribute to your control. This attribute informs template authors of the types and names of the elements in the ControlTemplate. Not every element in a ControlTemplate needs to be named in a TemplatePartAttribute. In fact, the fewer named elements, the better. But if you reference the element in code, you should use the TemplatePartAttribute.
For more information about designing a control that uses a ControlTemplate, see Guidelines for Designing Stylable Controls.
Designing for Designers
To receive support for custom WPF controls in the Windows Presentation Foundation (WPF) Designer for Visual Studio (for example, property editing with the Properties window), follow these guidelines. For more information on developing for the WPF Designer, see WPF Designer.
Dependency Properties
Be sure to implement CLR get and set accessors as described earlier, in "Use Dependency Properties." Designers may use the wrapper to detect the presence of a dependency property, but they, like WPF 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:
Have a public static readonly DependencyProperty of the form PropertyNameProperty that was creating using the RegisterAttached method. The property name that is passed to RegisterAttached must match PropertyName.
Implement a pair of public static CLR methods named SetPropertyName and GetPropertyName. Both methods should accept a class derived from DependencyProperty as their first argument. The SetPropertyName method also accepts an argument whose type matches the registered data type for the property. The GetPropertyName method should return a value of the same type. If the SetPropertyName method is missing, the property is marked read-only.
SetPropertyName 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.
Defining and Using Shared Resources for Your Control
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. When you put a control in the same assembly as an application, you are free to add global resources to the app.xaml file. 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:
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.
The application level—Resources defined by the Application object.
The theme level—Theme-level dictionaries are stored in a subfolder called Themes. The files in the Themes folder correspond to themes. For example, you might have Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml, and so on. You can also have a file called generic.xaml. When the system looks for a resource at the themes level, it first looks for it in theme-specific files and then looks for it in generic.xaml. For more information, see "Defining Resources at the Theme Level" later in this section.
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 call 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. The following example creates a LinearGradientBrush resource file called Dictionary1.XAML.
<ResourceDictionary
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://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've defined your dictionary, you need to merge it with your control's resource dictionary. You can do this using XAML or code.
The following example merges a resource dictionary by using XAML.
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
The disadvantage to this approach is that a ResourceDictionary object is created each time you reference it. 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. You can avoid this by creating a static class that merges the resources in code and returns the resulting 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;
}
The following example merges the shared resource with the resources of a custom control in the control's constructor before it calls InitilizeComponent. Because the SharedDictionaryManager.SharedDictionary is a static property, the ResourceDictionary is created only once. Because the resource dictionary was merged before InitializeComponent was called, the resources are available to the control in its XAML file.
public NumericUpDown()
{
this.Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary);
InitializeComponent();
}
Defining Resources at the Theme Level
WPF 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. 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. These files must be in a folder called 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 |
Windows theme |
---|---|
Classic.xaml |
"Classic" Windows 9x/2000 look on Windows XP |
Luna.NormalColor.xaml |
Default blue theme on Windows XP |
Luna.Homestead.xaml |
Olive theme on Windows XP |
Luna.Metallic.xaml |
Silver theme on Windows XP |
Royale.NormalColor.xaml |
Default theme on Windows XP Media Center Edition |
Aero.NormalColor.xaml |
Default theme on Windows Vista |
You do not need to define a resource for every theme. If a resource is not defined for a specific theme, then the control checks Classic.xaml for the resource. 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. The generic.xaml file is located in the same folder as the theme-specific resource dictionary files. Although generic.xaml does not correspond to a specific Windows theme, it is still a theme-level dictionary.
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. 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. (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.)
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)));
}
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. When you define a resource at the theme level, you must use a ComponentResourceKey as the key. 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>
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. You can accomplish that by adding the ThemeInfoAttribute to the assembly that contains the control. The 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.
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)]
Inheriting from UserControl vs. Using a ControlTemplate
Several samples demonstrate different methods for writing and packaging the NumericUpDown control. In NumericUpDown UserControl with DependencyProperty and RoutedEvent Sample, NumericUpDown inherits from UserControl; in NumericUpDown Custom Control with Theme and UI Automation Support Sample, NumericUpDown inherits from Control and uses a ControlTemplate. This section briefly describes some of the differences between the two and explains why the control that uses a ControlTemplate is more extensible.
The first major difference is that the NumericUpDown that inherits from UserControl does not use a ControlTemplate and the control that inherits directly from Control does. The following example shows the XAML of the control that inherits from UserControl. As you can see, the XAML is very similar to what you might see when you create an application and begin with a Window or Page.
<!--XAML for NumericUpDown that inherits from UserControl.-->
<UserControl x:Class="MyUserControl.NumericUpDown"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyUserControl">
<Grid Margin="3">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<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>
<RepeatButton Name="upButton" Click="upButton_Click"
Grid.Column="1" Grid.Row="0">Up</RepeatButton>
<RepeatButton Name="downButton" Click="downButton_Click"
Grid.Column="1" Grid.Row="1">Down</RepeatButton>
</Grid>
</UserControl>
The following example shows the ControlTemplate of the control that inherits from Control. Notice that the ControlTemplate looks similar to the XAML in the UserControl, with only a few differences in syntax.
<!--ControlTemplate for NumericUpDown that inherits from
Control.-->
<Style TargetType="{x:Type local:NumericUpDown}">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NumericUpDown}">
<Grid Margin="3">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border BorderThickness="1" BorderBrush="Gray"
Margin="2" Grid.RowSpan="2"
VerticalAlignment="Center" HorizontalAlignment="Stretch">
<TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Value}"
Width="60" TextAlignment="Right" Padding="5"/>
</Border>
<RepeatButton Command="{x:Static local:NumericUpDown.IncreaseCommand}"
Grid.Column="1" Grid.Row="0">Up</RepeatButton>
<RepeatButton Command="{x:Static local:NumericUpDown.DecreaseCommand}"
Grid.Column="1" Grid.Row="1">Down</RepeatButton>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The biggest difference in the two previous examples is that the one that uses the ControlTemplate has a customizable appearance and the one that inherits from UserControl does not. In the case where NumericUpDown inherits from UserControl, an application developer cannot do anything to change the appearance of the control. In fact, even though the NumericUPDown has a ControlTemplate property (because UserControl inherits from Control) if someone tries to set it, an exception will occur at run time. On the other hand, an application developer that uses the NumericUpDown that inherits from Control is free to create a new ControlTemplate for the control. For example, someone could create a ControlTemplate that placed the buttons to the left and right of the TextBlock instead of above and below it.
The difference between the two approaches is evident in the syntactical difference in the previous examples. The control that uses a ControlTemplate sets the Template property in a Style for NumericUpDown. This is a common method of creating control templates. By setting the Template property in a style, you are specifying that all instances of the control will use that ControlTemplate. Application developers are free to change the Template property of a NumericcUpDown to customize its appearance. In contrast, the XAML for the control that inherits from UserControl populates the Content property of NumericUpDown (<UserControl.Content> is implied in the XAML). If an application developer cannot change the Content property, the NumericUpDown is not usable.
Another difference between the samples is how the controls respond to the Up and Down buttons. The control that inherits from UserControl handles the click event, and the control that uses a ControlTemplate implements commands and binds to the commands in its ControlTemplate. As a result, an application developer who creates a new ControlTemplate for the NumericUpDown can also bind to the commands and retain the functionality of the control. If the ControlTemplate handled the click event instead of binding to commands, an application developer would need to implement event handlers when creating a new ControlTemplate, thereby breaking the encapsulation of the NumericUpDown.
Another difference is the syntax of the binding between the Text property of the TextBlock and the Value property. In the case of the UserControl, the binding specifies that the RelativeSource is the parent NumericUpDown control and binds to the Value property. In the case of the ControlTemplate, the RelativeSource is the control to which the template belongs. They accomplish the same thing, but it is worth mentioning that the binding syntax differs in the two examples.
In NumericUpDown Custom Control with Theme and UI Automation Support Sample, the NumericUpDown control is in an assembly that is separate from the application and defines and uses theme-level resources, but in NumericUpDown UserControl with DependencyProperty and RoutedEvent Sample, the NumericUpDown control is in the same assembly as the application and does not define or use theme-level resources.
Finally, NumericUpDown Custom Control with Theme and UI Automation Support Sample shows how to create an AutomationPeer for the NumericUpDown control. For more information on supporting UI Automation for custom controls, see UI Automation of a WPF Custom Control.
See Also
Concepts
Pack URIs in Windows Presentation Foundation
Other Resources
Change History
Date |
History |
Reason |
---|---|---|
May 2010 |
Clarified the process for finding theme resources. |
Customer feedback. |