Templates and styles complement each other, allowing users to redesign controls. To redesign the control, we must first understand the logical tree and visual tree of the control. WPF provides two classes for browsing logical trees and Visual Trees:
- System.Windows.LogicalTreeHelper
- System.Windows.Media.VisualTreeHelper
The LogicalTreeHelper class provides several methods:
Method name | explain |
---|---|
FindLogicalNode() | Finds a specific element by name, starting with the specified element and looking down the logical tree |
BringIntoView() | Scroll elements into view |
GetParent() | Gets the parent element of the specified element |
GetChildren() | Gets the child element of the specified element |
Similarly, the VisualTreeHelper class also provides getchildrencount(), getchild(), getparent(). The visual tree is an extended version of the logical tree, which divides the elements into smaller parts, which are represented by the derived classes of the FrameworkElement class.
Each control has a built-in method for determining how to render the control (as a more basic set of elements). This method is called control template and is defined with XAML markup blocks.
The control template defines the elements that make up the control. There are three types of templates in WPF, all of which inherit from the FrameworkTemplate base class. Control template (represented by ControlTemplate class), data template (represented by DataTemplate and HierachicalDataTemplate classes), panel template (represented by ItemsPanelTemplate class).
The following is a simplified version of the template for the normal Button class:
<ControlTemplate> <!--ButtonChrome Standardized visual appearance of element drawing buttons,Use to add graphic decoration around the button content--> <mwt:ButtonChrome Name="Chrome" ...> <!--ContentPresenter Contains the contents of the button--> <ContentPresenter Content="{TemplateBinding ContentControl.Content}" .../> </nwt:ButtonChrome> <ControlTemplate.Triggers> <Trigger Property="UIElement.IsKeyboardFocuse"> <!--TargetName Property represents a specific part that acts on a control template--> <Setter Property="mwt:ButtonChrome.RenderDefaulted" TargetName="Chrome"> <Setter.Value> <s:Boolean>True</s:Boolean> </Setter.Value> </Setter> <Trigger.Value> <s:Boolean>True</s:Boolean> </Trigger.Value> </Trigger> <Trigger Property="ToggleButton.IsChecked"> <Setter Property="mwt:ButtonChrome.RenderPressed" TargetName="Chrome"> <Setter.Value> <s:Boolean>True</s:Boolean> </Setter.Value> </Setter> <Trigger.Value> <s:Boolean>True</s:Boolean> </Trigger.Value> </Trigger> </ControlTemplate.Triggers> </ControlTemplate>
have access to Snoop The tool delves into the visual tree of the program. Or through a simple tool WPFControl
Create a control template
<Window.Resources> <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}"> <!--Rectange and Border Used to draw borders--> <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="2"> <!--All content controls require ContentPresenter Element representing "insert content here"--> <ContentPresenter RecognizesAccessKey="True"/> </Border> <!--add something--> </ControlTemplate> </Window.Resources> <!--In order to apply a custom template, you only need to set the value of the control Template attribute--> <Button Template="{StaticResource ButtonTemplate}">Custom Template Button</Button>
Templates and styles are similar and can change the appearance of elements. However, the style is limited to a much smaller scope, and the properties of the control can be adjusted, but a new visual tree composed of different elements can not be used to replace the original appearance of the control.
Control templates can be applied to multiple windows or to the entire program. To avoid multiple definitions, you can define template Resources in the Resources collection of the Application class. It is recommended to create a separate resource dictionary for each control and add it to the Resources collection of a specific window or Application (more commonly). You can do this using the MergedDictionaries collection.
<Application > <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Resources\Button.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application>
If you want to reuse these elements with different format details (usually color and font), you can extract these details from the template and put them into the style.
<!--Instead of hard coded colors, use template binding to extract information from control properties--> <ControlTemplate x:Key="CustomButtonTemplate" TargetType="{x:Type Button}"> <Border Name="Border" BorderThickness="2" CornerRadius="2" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}"> <Grid> <Rectangle Name="FocuseCue" Visibility="Hidden" Stroke="Black" StrokeThickness="1" StrokeDashArray="1 2" SnapsToDevicePixels="True"> </Rectangle> <ContentPresenter Margin="{TemplateBinding Padding}" RecognizesAccessKey="True"/> </Grid> </Border> <ControlTemplate.Triggers> <Trigger Property="IsKeyboardFocused" Value="True"> <Setter TargetName="FocusCue" Property="Visibility" Value="Visible"> </Setter> </Trigger> </ControlTemplate.Triggers> </ControlTemplate>
Apply this control template to the associated style, set the border and background color, and add a trigger to change the background color according to the state of the button:
<Style x:Key="CustomButtonStyle" TargetType="{x:Type Button}"> <Setter Property="Control.Template" Value="{StaticResource CustomButtonTemplate}"></Setter> <Setter Property="BorderBrush" Value="{StaticResource Border}"></Setter> <Setter Property="Background" Value="{StaticResource DefaultBackground}"></Setter> <Setter Property="TextBlock.Foreground" Value="White"></Setter> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="{StaticResource HighlightBackground}"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background" Value="{StaticResource PressedBackground}"/> </Trigger> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Background" Value="{StaticResource DisabledBackground}"/> </Trigger> </Style.Triggers> </Style>
To use this Template, you need to set the Style property of the button instead of the Template property:
<Button Margin="10" Style="{StaticResource CustomButtonStyle}">Custom Template</Button>
Ideally, you can keep all triggers in the control template because they represent the behavior of the control and use simple to set basic properties. However, if you want the style to set the color scheme, it is impossible. If a trigger is set in both the control template and the style, the style trigger has priority.
A resource dictionary that contains a combination of type based styles is often referred to as a theme. Themes allow you to re skin all controls in an existing program without changing the user interface tags. You need to add a resource dictionary to the project and merge it into the Application.Resources collection of the App.xaml file.
<Application> <Application.Resources> <ResourceDictionary Source="ExpressionDark.xaml"/> </Application.Resources> </Application>
When the user is allowed to select a skin, the key is to retrieve the ResourceDictionary object:
ResourceDictionary newDictionary = new ResourceDictionary(); newDictionary.Source = new Uri("Resources/GradientButtonVariant.xaml", UriKind.Relative); // Window this.Resources.MergedDictionaries[0] = newDictionary; // Application Application.Current.Resources.MergedDictionaries[0] = newDictionary;