WPF (four) WPF template

1 The concept of a template

​ In WPF, a control is just a carrier of data and algorithmic behavior, which is an abstract concept. As for the appearance and behavior of the control itself, the presentation of the control data is defined by the Template. By introducing the template (Template), Microsoft decouples the "content" and "form" of the data and the algorithm, and we can easily create , Modify, adjust what the control looks like, what behavior it has, how the data is displayed, etc. That's why the shape of a Button or TextBox is rectangular by default, because it is defined in its default template (it is the template that determines that the TextBox is a square input box that can enter data). Going deep into the control, each control itself is a UI element tree, and its interior is composed of many sub-element nodes. WPF templates mainly include two types: ControlTemplate (control template) and DataTemplate (data template).

  • ControlTemplate: The control template is defined by the Template property of the control, which is used to determine the overall appearance of the control (the overall structure of the control UI element tree) and functions (triggers and events). The root of the control tree generated by ControlTemplate is the target control of ControlTemplate, and the Template property value of this templated control is the ControlTemplate instance.
  • DataTemplate: The data template is defined by the xxxTemplate property of the control (such as ContentTemplate of ContentControl, ItemTemplate of ItemsControl, etc.), which is used to determine the presentation mode of the control data. DataTemplate can contain UI elements and data, and also constitutes a UI element tree. The root of the control tree generated by DataTemplate is a ContentPresenter control. The ContentPresenter control has only the ContentTemplate property and no Template property. It is used to carry the DataTemplate through the Content and mount it in the ControlTemplate. The ContentPresenter control is a node on the ControlTemplate control tree, so the DataTemplate control tree is a subtree of the ControlTemplate control tree.

insert image description here

2 Definition of template

2.1 Data Template DataTemplate

The commonly used positions of DataTemplate mainly include three places, which are as follows. The most important point is to set Binding for each control in the DataTemplate, telling each element in the DataTemplate which attribute of the data should be concerned.

  • ContentTemplate property of ContentControl: set the appearance and display mode of Content data of ContentControl control content, which is rarely used. For example, the text in the Button, the Content of the UserControl, etc.
  • The ItemTemplate property of ItemsControl: Get or set the DataTemplate used to display each list [data item entry], which can be regarded as bound to the data template of the internal xxxItem (mainly)
  • The CellTemplate property of GridViewColumn: customize the appearance of each cell data of GridViewColumn
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <!--定制“数据详情”的数据展示外观-->
    <DataTemplate x:Key="detailViewTemplate">
        <Border BorderBrush="Black" CornerRadius="6">
            <StackPanel Margin="5">
                <Border Width="380" Height="200" Background="{Binding Code}"/>
                <StackPanel Orientation="Horizontal" Margin="5,0">
                    <TextBlock Text="Color Name:" FontSize="20" FontWeight="Bold"/>
                    <TextBlock Text="{Binding Name}" FontSize="20" Margin="5,0"/>
                </StackPanel>
                <StackPanel Orientation="Horizontal" Margin="5,0">
                    <TextBlock Text="Color Code:" FontWeight="Bold"/>
                    <TextBlock Text="{Binding Code}" Margin="5,0"/>
                </StackPanel>
            </StackPanel>
        </Border>
    </DataTemplate>

    <!--定制“数据列表条目”的数据展示外观-->
    <DataTemplate x:Key="ListItemViewTemplate">
        <StackPanel Orientation="Horizontal" Margin="5,0">
            <Border Width="20" Height="20" Background="{Binding Code}"/>
            <StackPanel Margin="5,10">
                <TextBlock Text="{Binding Name}" FontSize="16" FontWeight="Bold"/>
                <TextBlock Text="{Binding Code}" FontSize="14"/>
            </StackPanel>
        </StackPanel>
    </DataTemplate>

</ResourceDictionary>
<Window x:Class="WPF_Demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Demo"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="600">

    <StackPanel Orientation="Horizontal" x:Name="stackPanel" Margin="5">
        <UserControl ContentTemplate="{StaticResource detailViewTemplate}" Content="{Binding SelectedItem,ElementName=listBoxColors}"/>
        <StackPanel Width="180">
            <ListBox x:Name="listBoxColors" Height="280" Margin="5,0" ItemTemplate="{StaticResource ListItemViewTemplate}"/>
            <Button Content="Add" Margin="5" x:Name="bntAdd" Click="bntAdd_Click"/>
        </StackPanel>
    </StackPanel>
</Window>
  • For ContentControl (UserControl here): using ContentTemplate is equivalent to providing a template for the display of the Content content of the control. And ContentPresenter is the carrier of DataTemplate mounted and displayed in ControlTemplate. It is the root element of DataTemplate. After setting the Content content to bind data, the template will automatically bind the Content property and ContentTemplate property of the control ContentPresenterto it (context), so in In ContentControl, we can directly use Binding to obtain context data.
  • For ItemsControl (ListBox here): Using ItemTemplate is equivalent to providing a template for the display of the content data of each data item in the control. When the ItemSource of the ListBox is assigned, it will automatically create an equivalent item container xxxItem (inherited from ContentControl) for each data element, and automatically use Binding to establish an association and binding between each data item container and the data element (will Each list collects data as the DataContext of each Item).
    • Take ListBoxItem as an example. ListBoxItem inherits from ContentControl. Its Content property carries each data item in the list. Its ContentTemplate data template comes from ItemTemplate of ItemsControl as a template for displaying data. And each piece of data of ItemsSource is automatically bound as the context DataContext of Item when it is created.
    • Similarly, ItemsPresenter is also the carrier for the entire DataTemplate of the list control to be mounted and displayed in the ControlTemplate, and is the root element of the DataTemplate (a series of Items that define the DataTemplate are mounted to the ItemsPresenter, and then the ItemsPresenter is mounted and displayed in the ControlTemplate UI element tree of the list control ).
namespace WPF_Demo
{
    
    
    public partial class MainWindow : Window
    {
    
    
        ObservableCollection<ColorItem> colorList;
        public MainWindow()
        {
    
    
            InitializeComponent();
            //列表数据源
            colorList = new ObservableCollection<ColorItem>()
            {
    
    
                new ColorItem(){
    
    Name="浅绿色",Code="#33FF00"},
                new ColorItem(){
    
    Name="深绿色",Code="#006600"},
                new ColorItem(){
    
    Name="红色",Code="#FF0033"},
                new ColorItem(){
    
    Name="紫色",Code="#9900FF"}
            };
            //设置上下文
            this.listBoxColors.ItemsSource = colorList;
        }

        private void bntAdd_Click(object sender, RoutedEventArgs e)
        {
    
    
        	//动态添加数据
            colorList.Add(new ColorItem() {
    
     Name = "未知", Code = "#000000" });
        }
    }

    public class ColorItem
    {
    
    
        public string Name {
    
     get; set; }
        public string Code {
    
     get; set; }
    }
}

2.2 Control Template ControlTemplate

​ ControlTemplate is above DataTemplate and is used to organize the structure and behavior of the entire control UI element tree. Internally, the Presenter is used to bind and mount the data content of the DataTemplate. ControlTemplate (control template) is not only used to define the appearance and style of the control, but also to modify the behavior and response animation of the control through the trigger (ControlTemplate.Triggers) of the control template.

(1) Button's ControlTemplate source code

insert image description here

  • TargetType : Get or set the type targeted by this ControlTemplate , the default is null. Note that if the TargetType is included in the template definition , the ContentPresenter attribute cannot be null.
  • ContentPresenter: Mount the control of DataTemplate to realize the display of data content. Equivalent to
  • TemplateBinding: ControlTemplate is finally applied to a control. This control is called a template target control. The control elements in ControlTemplate can use TemplateBinding to associate/bind their properties one-way to the property value of the target control. TemplateBindingis an optimized form of binding similar to {Binding RelativeSource={RelativeSource TemplatedParent}}bindings constructed using . TemplateBindingCan be used to bind various parts of the template to various properties of the control.

(2) ControlTemplate source code of ListBox
insert image description here

There are two main steps to define ItemsControl:

  • 1. Set the ItemsPanel container to accommodate the outermost container of the list layout 2. Define the DataTemplate of the child items

  • ItemsPanel: ItemsPanelTemplate type, get or set the template, the template defines the layout of the item control panel. is the outer container that holds the list layout. Only Panel family elements can be used. The default value of ItemsControl is a StackPanel

img

3 Use of templates

​ Here we take the custom template of ItemsControl as an example, and use two practical examples to introduce the actual application of the template.

  • IsItemsHost Property: In this example, an important property that is required is the IsItemsHost property. The IsItemsHost property is used to indicate where the generated element should be placed in the template of an ItemsControl such as a ListBox control that handles a list of items. If you set this property of the StackPanel to true, all items added to the ListBox will go into the StackPanel. Note that this property is only valid for Panel types. Note that if you specify a panel in the ControlTemplate in this way and mark it as IsItemsHost, users of the control cannot replace the ItemsPanel without using the ControlTemplate. So don't do it this way unless you're sure you have to use a template to replace the panel.
  • ItemsPresenter and ContentPresenter: Alternatively, you can use the ItemsPresenter element to mark the position of items, and then specify the ItemsPanelTemplate by setting the ItemsPanel property. If you want to create a template for ContentControl (such as Button), the corresponding element is ContentPresenter. Likewise, placing this element in a ControlTemplate of type ContentControl indicates where the content should be displayed.

(1) Example 1

    <!-- 1.声明自定义ItemsControl控件 -->
	<ItemsControl ItemsSource="{Binding Scans}">
        <!-- 2.定义ItemsControl的整体样式模板ControlTemplate -->
        <ItemsControl.Template>
            <ControlTemplate TargetType="{x:Type ItemsControl}">
                <ScrollViewer HorizontalAlignment="Stretch" VerticalScrollBarVisibility="Hidden">
                    <!--IsItemsHost 属性用于指示在 ItemsControl(如处理项列表的 ListBox 控件)的模板中,生成的元素应放在什么位置。用于代替ItemsPresenter -->
                    <StackPanel IsItemsHost="True" Orientation="Vertical" />
                </ScrollViewer>
            </ControlTemplate>
        </ItemsControl.Template>
         <!-- 3.定义ItemsControl每个数据项的展示模板DataTemplate -->
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <ContentControl x:Name="CardPresenter">
                    <RadioButton
                        Height="35"
                        HorizontalContentAlignment="Center"
                        VerticalContentAlignment="Center"
                        Content="{Binding ScanName}"
                        FontSize="20"
                        Foreground="White"
                        GroupName="scanbtn">
                        <RadioButton.Style>
                            <Style BasedOn="{StaticResource ScanButtonStyle}" TargetType="RadioButton">
                                <Setter Property="Background" Value="{Binding Status, Converter={StaticResource bgCV}}" />
                            </Style>
                        </RadioButton.Style>
                        <!-- 声明RadioButton的交互触发器触发行为 -->
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="Click">
                                <i:InvokeCommandAction Command="{Binding ScanClickCommand, Source={x:Static vm:EDESPageStatus.Instance}}" PassEventArgsToCommand="True" />
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </RadioButton>
                </ContentControl>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

(2) Example 2

	<!-- 1.声明自定义ItemsControl控件 -->
	<ItemsControl
        x:Name="itemsControl"
        Padding="0,5,10,5"
        DataContext="{x:Static vm:ExplorePageStatus.Instance}"
        ItemsSource="{Binding Cards}">
        <!-- 2.定义ItemsControl的整体样式模板ControlTemplate -->
        <ItemsControl.Template>
            <ControlTemplate TargetType="{x:Type ItemsControl}">
                <Border
                    Padding="{TemplateBinding Padding}"
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    SnapsToDevicePixels="True">
                    <ScrollViewer VerticalScrollBarVisibility="Hidden">
                        <!-- 使用 ItemsPresenter 元素来标记数据项列表在整体样式中的的位置(挂载) -->
                        <ItemsPresenter HorizontalAlignment="Center" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                    </ScrollViewer>
                </Border>
            </ControlTemplate>
        </ItemsControl.Template>
        <!-- 3.定义对数据项的布局进行控制的面板Panel(控制数据项的排布方式,作用在ItemsPresenter内数据项列表外) -->
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel Orientation="Horizontal">
                    <WrapPanel.Style>
                        <Style TargetType="WrapPanel">
                            <Setter Property="ItemWidth" Value="420" />
                            <Setter Property="ItemHeight" Value="326.67" />
                        </Style>
                    </WrapPanel.Style>
                </WrapPanel>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <!-- 4.定义ItemsControl每个数据项的展示模板DataTemplate -->
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <!--内部放置UI元素以及Binding数据(默认来自ItemsSource的每一项)-->
                <Grid x:Name="OuterGrid">
                    <Grid x:Name="InnerGrid" DataContext="{x:Static vm:ExplorePageStatus.Instance}">
                        <Border
                            x:Name="ButtonBorder"
                            Margin="8,4.8"
                            Background="Transparent"
                            BorderBrush="{c:Binding 'IsChecked ? \'Yellow\' : \'Transparent\'',
                                                    Mode=OneWay}"
                            BorderThickness="3"
                            DataContext="{Binding DataContext, ElementName=OuterGrid}">
                            <Grid DataContext="{Binding DataContext, ElementName=OuterGrid}">
                                <Canvas Panel.ZIndex="1">
                                    <Label
                                        Canvas.Left="10"
                                        Canvas.Top="12"
                                        Width="40"
                                        Height="19.2"
                                        Padding="0"
                                        HorizontalContentAlignment="Center"
                                        VerticalContentAlignment="Center"
                                        Background="{c:Binding '(Phase == e:PhaseEnum.ED ? \'#FF6B6B\' : \'#44D7B6\')',
                                                               Mode=OneWay}"
                                        BorderThickness="0"
                                        Content="{c:Binding '(Phase == e:PhaseEnum.ED ? \'ED\' : \'ES\')',
                                                            Mode=OneWay}"
                                        FontFamily="PingFangSC-Semibold"
                                        FontSize="19px"
                                        FontWeight="SemiBold"
                                        Foreground="White" />
                                    <Label
                                        Canvas.Left="5"
                                        Canvas.Bottom="10"
                                        Width="30"
                                        Height="20"
                                        Padding="0"
                                        HorizontalContentAlignment="Center"
                                        VerticalContentAlignment="Center"
                                        Background="{c:Binding '(LVChecked ? \'#9ACD32\' : \'#C0C0C0\')',
                                                               Mode=OneWay}"
                                        BorderThickness="0"
                                        Content="LV"
                                        FontFamily="PingFangSC-Semibold"
                                        FontSize="16px"
                                        FontWeight="SemiBold"
                                        Foreground="White" />
                                    <Label
                                        Canvas.Left="40"
                                        Canvas.Bottom="10"
                                        Width="30"
                                        Height="20"
                                        Padding="0"
                                        HorizontalContentAlignment="Center"
                                        VerticalContentAlignment="Center"
                                        Background="{c:Binding '(LAChecked ? \'#9ACD32\' : \'#C0C0C0\')',
                                                               Mode=OneWay}"
                                        BorderThickness="0"
                                        Content="LA"
                                        FontFamily="PingFangSC-Semibold"
                                        FontSize="16px"
                                        FontWeight="SemiBold"
                                        Foreground="White" />
                                    <Label
                                        Canvas.Left="75"
                                        Canvas.Bottom="10"
                                        Width="30"
                                        Height="20"
                                        Padding="0"
                                        HorizontalContentAlignment="Center"
                                        VerticalContentAlignment="Center"
                                        Background="{c:Binding '(RVChecked ? \'#9ACD32\' : \'#C0C0C0\')',
                                                               Mode=OneWay}"
                                        BorderThickness="0"
                                        Content="RV"
                                        FontFamily="PingFangSC-Semibold"
                                        FontSize="16px"
                                        FontWeight="SemiBold"
                                        Foreground="White" />
                                    <Label
                                        Canvas.Left="110"
                                        Canvas.Bottom="10"
                                        Width="30"
                                        Height="20"
                                        Padding="0"
                                        HorizontalContentAlignment="Center"
                                        VerticalContentAlignment="Center"
                                        Background="{c:Binding '(RAChecked ? \'#9ACD32\' : \'#C0C0C0\')',
                                                               Mode=OneWay}"
                                        BorderThickness="0"
                                        Content="RA"
                                        FontFamily="PingFangSC-Semibold"
                                        FontSize="16px"
                                        FontWeight="SemiBold"
                                        Foreground="White" />
                                </Canvas>
                                <Image
                                    Panel.ZIndex="0"
                                    Source="{c:Binding '(Phase == e:PhaseEnum.ED ? EDImage : ESImage)',
                                                       Mode=OneWay}"
                                    Stretch="Uniform" />
                            </Grid>
                        </Border>
                    </Grid>
                    <!--每个数据项的设备输入行为-->
                    <Grid.InputBindings>
                        <MouseBinding
                            Command="{Binding Source={x:Static vm:ExplorePageStatus.Instance}, Path=KeyFrameDetailsCommand}"
                            CommandParameter="{Binding ElementName=OuterGrid, Path=DataContext}"
                            MouseAction="LeftClick" />
                    </Grid.InputBindings>

                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

4. The internal principle of the template mechanism

(1) Principle: The ItemsPresenter in the default Template of ItemsControl only acts as a placeholder (placeholder). Its main role is to receive the ItemsPanelTemplate template of ItemsControl and apply this template when ItemsControl applies the template. At this time, when the ItemsPresenter in the Template template of an ItemsControl applies the ItemsPanel template of the ItemsControl, it will set the TemplateParent of the Panel class control in the template to the ItemsControl, and mark its IsItemsHost property as true. Another usage of ItemsControl is to ignore ItemsPanel and directly specify an "ItemsPanel" in its Template, which is the above-mentioned way of specifying IsItemsHost. At this time, the settings of the ItemsPanel template will be ignored directly. However, you must set the IsItemsHost of this Panel to True at this time, otherwise the ItemsControl will not find a suitable ItemsPanel to display the list items.

(2) Summary: Let's sort out the template application mechanism of ItemsControl from the top to the bottom, that is, when an ItemsControl applies a template, it will first apply the Template template (ControlTemplate type) to generate its own visual tree (Control class template mechanism), and then the ItemsPresenter in the Template template applies the ItemsPanel template (ItemsPanelTemplate type) of its TemplateParent (that is, the ItemsControl) to generate a visual tree, and places the visual tree in the position of the ItemsPresenter (the ItemsPresenter acts as a placeholder at this time role). When the ItemsPanel template is applied, the TemplateParent of this panel will be pointed to this ItemsControl, and its IsItemsHost property will be marked as true. ItemsControl's ItemContainerGeneror traverses its own ItemsInternal list and generates a container (ItemContainer) for each list item (item), and "forwards" the ItemTemplate template of ItemsControl to this container, so that this container can apply the template (ItemTemplate ), generate a visual tree defined by this ItemTemplate for its corresponding data item (item).

Guess you like

Origin blog.csdn.net/qq_40772692/article/details/126428787