1, Foreword
Control is a dual carrier of data content and algorithm content. The data content expression form of the control allows the user to intuitively see the data, and the algorithm content form allows the user to operate the logic conveniently. As a "form of expression", each control is born to realize a user's operation algorithm and intuitively display some data. That is, the control is determined by "algorithm content" and "data content" (content determines form):
- Control's "algorithm content": that is, the functions that the control can realize. They are a set of algorithm logic.
- Control's "data content": that is, what is the specific content displayed by the control.
In previous GUI development technologies (such as MFC, Windows Forms and ASP.NET), the logic and data inside the control are fixed, and the appearance of the control must be changed through the control properties, which cannot change the internal structure of the control. If you want to extend the functionality of a control, you must create a subclass or user control of the control. The root cause of this problem is that the algorithm content and data content are too closely coupled.
In order to solve the above problems, WPF t launched a Template. WPF templates fall into two categories:
- ControlTemplate: the expression of algorithm content. The internal structure of the control is more in line with business logic and more convenient for users to operate. It determines how the control "looks".
- DataTemplate: the presentation form of content data, which determines what a piece of data looks like.
That is, Template is the "coat" -- ControlTemplate is the "coat" of control and DataTemplate is the "coat" of data.
2, Data template -- the cloak of data
DataTemplate is commonly used in three places:
- ContentTemplate attribute of ContentControl: equivalent to putting a coat on the content of ContentCtrol.
- ItemsTemplate property of ItemsControl: equivalent to putting a coat on the data item of ItemsControl.
- CellTemplate property of GridViewColumn: equivalent to dressing the data in the cell of GridViewColumn.
For example, we realize the following functions:
Let's define a data model first:
public class Unit { public string Year { get; set; } public uint Price { get; set; } } }
Then define the DataTemplate and bind it to the ListBox, as follows:
<Window.Resources> <x:Array x:Key="ListName" Type="{x:Type local:Unit}"> <local:Unit Year="2001" Price="60"/> <local:Unit Year="2002" Price="120"/> <local:Unit Year="2003" Price="100"/> <local:Unit Year="2004" Price="200"/> </x:Array> <DataTemplate x:Key="DataTemplateYear"> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Year,StringFormat={}{0}year}"></TextBlock> <Rectangle Margin="2,0,2,0" Fill="SlateBlue" Width="{Binding Path=Price}"></Rectangle> <TextBlock Text="{Binding Path=Price}"></TextBlock> </StackPanel> </DataTemplate> </Window.Resources> <Grid> <ListBox ItemsSource="{StaticResource ListName}" ItemTemplate="{DynamicResource DataTemplateYear}"></ListBox> </Grid>
3, ControlTemplate, the coat of control
ControlTemplate functions:
- Change the appearance of the control by changing the ControlTemplate, so that it has a better user experience
- Designers and programmers can work in parallel. Programmers can complete the development work first and replace it after the Designer completes the ControlTemplate.
It should be noted that when you edit the ControlTemplate, you actually include the ControlTemplate in the Style. For example, we set a rounded TextBox and a rounded Button as follows:
<Style x:Key="RoundCornerTextBoxStyle" BasedOn="{x:Null}" TargetType="{x:Type TextBox}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TextBox}"> <Border CornerRadius="5" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <ScrollViewer x:Name="PART_ContentHost"></ScrollViewer> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style x:Key="RoundCornerButtonStyle" BasedOn="{x:Null}" TargetType="{x:Type Button}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Border CornerRadius="5" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"></ContentPresenter> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
Note: the intersection of Logical Tree and Visual Tree is ControlTemplate.
Style contains setter and trigger. Setter sets the static appearance style of the control, that is, the property setter of the control. Trigger sets the behavior style of the control, which is reflected by the response to external stimuli.
Setter is an attribute setter, which sets attributes in the way of "attribute name = attribute value".
Trigger: basic trigger, similar to setter. Property is the name of the property that trigger focuses on, and Value is the trigger condition.
Setter and Trigger use the following methods. We implement the fillet button and realize the function that the button turns gray when the mouse moves over the button:
<Window.Resources> <Style x:Key="RoundCornerButtonStyle" BasedOn="{x:Null}" TargetType="{x:Type Button}"> <Setter Property="Background" > <Setter.Value> <LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> <GradientStop Color="SeaGreen" Offset="0.3"></GradientStop> <GradientStop Color="Teal" Offset="0.6"></GradientStop> <GradientStop Color="Yellow" Offset="1.1"></GradientStop> </LinearGradientBrush> </Setter.Value> </Setter> <Setter Property="FontSize" Value="24"></Setter> <Setter Property="Foreground" Value="White"></Setter> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Border CornerRadius="5" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"></ContentPresenter> </Border> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="SlateGray"></Setter> <Setter Property="FontSize" Value="32"></Setter> <Setter Property="Foreground" Value="GreenYellow"></Setter> </Trigger> </Style.Triggers> </Style> </Window.Resources> <StackPanel> <Button Margin="10" Height="100" Style="{DynamicResource RoundCornerButtonStyle}" Content="Update"></Button> </StackPanel> </Window>
MultiTrigger: it can only be triggered when multiple conditions are true at the same time. Specific examples are as follows. When CheckBox is selected and the content is "Test", the function of gray and enlarged font display is triggered:
<Style TargetType="{x:Type CheckBox}"> <Style.Triggers> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsChecked" Value="true"></Condition> <Condition Property="Content" Value="Test"></Condition> </MultiTrigger.Conditions> <MultiTrigger.Setters> <Setter Property="Foreground" Value="DarkGray"></Setter> <Setter Property="FontSize" Value="18"></Setter> </MultiTrigger.Setters> </MultiTrigger> </Style.Triggers> </Style>
DataTrigger: performs some judgment based on data. Specific examples are as follows. When the length of the text content of the input TextBox is less than 3, the border of the TextBox will be red:
public class LengthToBooleanConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (int.TryParse(value.ToString(),out var length) && length>3) { return true; } return false; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
<local:LengthToBooleanConverter x:Key="LTBC"></local:LengthToBooleanConverter>
<Style TargetType="{x:Type TextBox}"> <Style.Triggers> <DataTrigger Binding ="{Binding RelativeSource={RelativeSource Self},Path=Text.Length,Converter={StaticResource LTBC}}" Value="false"> <Setter Property="BorderBrush" Value="Red"></Setter> <Setter Property="BorderThickness" Value="1"></Setter> </DataTrigger> </Style.Triggers> </Style>
MultiDataTrigger: it can be triggered only when multiple data conditions are met at the same time. Specific examples are as follows. Use ListBox to display a column of Student data. When Student's Name = "dwayne" and Age=10, the column will be highlighted:
<x:Array x:Key="ListStudent" Type="{x:Type local:Student}"> <local:Student Name="dwayne" Age="10"></local:Student> <local:Student Name="Tom" Age="10"></local:Student> <local:Student Name="Jho" Age="22"></local:Student> </x:Array>
<Style TargetType="{x:Type ListBoxItem}"> <Setter Property="ContentTemplate"> <Setter.Value> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Width="100" Text="{Binding Path=Name}"></TextBlock> <TextBlock Width="100" Text="{Binding Path=Age}"></TextBlock> </StackPanel> </DataTemplate> </Setter.Value> </Setter> <Style.Triggers> <MultiDataTrigger> <MultiDataTrigger.Conditions> <Condition Binding="{Binding Path=Name}" Value="dwayne"></Condition> <Condition Binding ="{Binding Path=Age}" Value="10"></Condition> </MultiDataTrigger.Conditions> <MultiDataTrigger.Setters> <Setter Property="Background" Value="Orange"></Setter> </MultiDataTrigger.Setters> </MultiDataTrigger> </Style.Triggers> </Style>
EventTrigger: it is the most special trigger. First, it is triggered by an event, and then a set of animation is triggered, not a Setter. Specific examples are as follows: after the mouse enters the Button interface, the Button is enlarged, the font is enlarged, and it is restored after leaving:
<Style TargetType="{x:Type Button}"> <Style.Triggers> <Trigger Property="IsMouseOver" Value="true"> <Setter Property="FontSize" Value="24"></Setter> </Trigger> <EventTrigger RoutedEvent="MouseEnter"> <BeginStoryboard> <Storyboard> <DoubleAnimation To="150"></DoubleAnimation> <DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Height"></DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger> <EventTrigger RoutedEvent="MouseLeave"> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Width"></DoubleAnimation> <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Height"></DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger> </Style.Triggers> </Style>
4, Relationship and application of DataTemplate and ControlTemplate
All templates ultimately act on the control, which is the target control of the Template, also known as Template Control.
The goal of DataTemplate is data, but the display of data needs a carrier. This carrier is generally on the ContentPresenter object, which has only ContentTemplate but no Template attribute, which shows that ContentPresenter is a group of controls specially used to host DataTemplate. The specific relationship is as follows:
That is, the root of the control tree generated by ControlTemplate is the target control of ControlTemplate, and the Template property value of the target control is the instance of ControlTemplate. The root of the control tree generated by DataTmeplate is the ContentPresenter control, and the value of the ContentTemplate property of the ControlPresenter control is the instance of DataTemplate.