• WPF(四) WPF 模板


    1 模板的概念

    ​ 在WPF中,控件只是一个数据和算法行为的载体,是个抽象的概念。至于控件本身的外观和行为、控件数据的呈现方式都是靠 Template 来具体定义的,通过引入模板(Template)微软将数据和算法的“内容”与“形式”解耦了,我们可以轻松的创造、修改、调整控件长什么样、有什么行为、数据如何展示等。这就是为什么默认情况下 Button 或 TextBox 形状是矩形的,因为它是在其默认模板中定义(正是模板决定了TextBox是方方正正的输入框,可以输入数据)。深入到控件内部,每个控件本身是一棵 UI元素树,其内部都是由很多子元素节点挂载组成。WPF的模板主要包括 ControlTemplate(控件模板)和DataTemplate(数据模板)两种类型。

    • ControlTemplate:控件模板由控件的Template属性定义,用于决定控件的整体外观(控件UI元素树整体结构)与功能(触发器与事件)。由ControlTemplate生成的控件树其树根就是ControlTemplate的目标控件,此模板化控件的Template属性值就是这个ControlTemplate实例。
    • DataTemplate:数据模板由控件的xxxTemplate属性定义(比如ContentControl的ContentTemplate、ItemsControl的ItemTemplate等),用于决定控件数据的呈现方式。DataTemplate 可以包含 UI 元素和数据,也构成了一颗UI元素树。由 DataTemplate 生成的控件树其树根是一个 ContentPresenter 控件,ContentPresenter 控件只有ContentTemplate属性没有Template属性,用于通过Content来承载DataTemplate,在ControlTemplate中挂载。ContentPresenter 控件是 ControlTemplate 控件树上的一个结点,所以DataTemplate 控件树是 ControlTemplate 控件树的一棵子树。

    在这里插入图片描述

    2 模板的定义

    2.1 数据模板 DataTemplate

    DataTemplate常用的位置主要包括三处,分别如下。最重要的一点是为DataTemplate中的每个控件设置Binding,告诉DataTemplate中各个元素应该关注数据的哪个属性。

    • ContentControl的ContentTemplate属性:设置ContentControl控件内容Content数据的外观展示方式,使用较少。比如Button内的文字、UserControl的Content等
    • ItemsControl的ItemTemplate属性:获取或设置用来显示每个列表[数据项条目]的 DataTemplate,该属性可以视为与内部 xxxItem 的数据模板绑定(主要)
    • GridViewColumn的CellTemplate属性:定制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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 对ContentControl来说(这里的UserControl):使用ContentTemplate相当于给控件的Content内容的展示提供了模板。而ContentPresenter又是DataTemplate在ControlTemplate中挂载展示的载体,是DataTemplate的根元素,设置Content内容绑定数据之后,模板会自动将控件的 Content 属性和ContentTemplate属性绑定到其ContentPresenter上(上下文),所以在ContentControl中我们直接使用Binding就可以获取上下文数据。
    • 对ItemsControl来说(这里的ListBox):使用ItemTemplate相当于给控件的每个数据条目内容数据的展示提供了模板。当ListBox的ItemSource被赋值时,会自动为每一个数据元素创建等量的条目容器 xxxItem(继承自ContentControl ),并自动使用Binding在每个数据条目容器与数据元素之间建立关联和绑定(将每个列表集合数据作为每条Item的DataContext)。
      • 以ListBoxItem为例,ListBoxItem继承自ContentControl,其Content属性承载了列表的每条数据条目,其ContentTemplate数据模板来自于ItemsControl的ItemTemplate,作为展示数据的模板。并在创建时自动绑定了ItemsSource的每条数据作为Item的上下文DataContext。
      • 同样,ItemsPresenter也是列表控件的整个DataTemplate在ControlTemplate中挂载展示的载体,是DataTemplate的根元素(一系列定义了DataTemplate 的 Item挂载到ItemsPresenter,然后ItemsPresenter在列表控件的ControlTemplate UI元素树中挂载展示)。
    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; }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    2.2 控件模板 ControlTemplate

    ​ ControlTemplate在DataTemplate之上,用于组织整个控件UI元素树的结构和行为。其内部通过Presenter来绑定挂载DataTemplate的数据内容Content。ControlTemplate(控件模板)不仅是用于来定义控件的外观、样式, 还可通过控件模板的触发器(ControlTemplate.Triggers)修改控件的行为、响应动画等。

    (1) Button的ControlTemplate源码

    在这里插入图片描述

    • TargetType : 获取或设置此 ControlTemplate 所针对的类型,默认为null。注意如果模板定义中包含 TargetType,则 ContentPresenter 属性不能为 null
    • ContentPresenter:挂载DataTemplate的控件,实现了数据内容的显示。等价于
    • TemplateBinding:ControlTemplate最终被应用到一个控件上,这个控件被称为模板目标控件,ControlTemplate 里的控件元素可以使用TemplateBinding将自己的属性单向关联/绑定到目标控件的属性值上。TemplateBinding 是绑定的优化形式,类似于使用 {Binding RelativeSource={RelativeSource TemplatedParent}} 构造的绑定。TemplateBinding 可用于将模板的各个部分绑定到控件的各个属性。

    (2) ListBox的ControlTemplate源码
    在这里插入图片描述

    定义 ItemsControl 主要分两个步骤:

    • 1.设置ItemsPanel容器, 用于容纳列表布局的最外层容器 2.定义子项的DataTemplate

    • ItemsPanel :ItemsPanelTemplate类型,获取或设置模板,该模板定义对项的布局进行控制的面板。是容纳列表布局的外层容器。只能使用Panel族元素。 ItemsControl 的默认值是一个 StackPanel

    img

    3 模板的使用

    ​ 我们这里以 ItemsControl 的自定义模板为例,用两个实际例子介绍模板的实际应用。

    • IsItemsHost 属性:在此示例中,一个必需的重要属性是 IsItemsHost 属性。IsItemsHost 属性用于指示在 ItemsControl(如处理项列表的 ListBox 控件)的模板中,生成的元素应放在什么位置。如果将 StackPanel 的这一属性设置为 true,则添加到 ListBox 的所有项都将进入 StackPanel。请注意,此属性只对 Panel 类型有效。请注意,如果以这种方式在 ControlTemplate 中指定一个面板并将其标记为 IsItemsHost,控件的用户不使用 ControlTemplate 就无法替换 ItemsPanel。因此,除非您确信必须使用模板才能替换面板,否则不要采用这种方式。
    • ItemsPresenter 和 ContentPresenter:此外,您也可以使用 ItemsPresenter 元素来标记项的位置,然后通过设置 ItemsPanel 属性来指定 ItemsPanelTemplate。如果要创建 ContentControl(如 Button)的模板,则对应元素为 ContentPresenter。同样,将此元素放置到 ContentControl 类型的 ControlTemplate 中,可以指示内容应在什么位置显示。

    (1)实例1

        
    	<ItemsControl ItemsSource="{Binding Scans}">
            
            <ItemsControl.Template>
                <ControlTemplate TargetType="{x:Type ItemsControl}">
                    <ScrollViewer HorizontalAlignment="Stretch" VerticalScrollBarVisibility="Hidden">
                        
                        <StackPanel IsItemsHost="True" Orientation="Vertical" />
                    ScrollViewer>
                ControlTemplate>
            ItemsControl.Template>
             
            <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">
                                    "Background" Value="{Binding Status, Converter={StaticResource bgCV}}" />
                                Style>
                            RadioButton.Style>
                            
                            <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    (2)实例2

    	
    	<ItemsControl
            x:Name="itemsControl"
            Padding="0,5,10,5"
            DataContext="{x:Static vm:ExplorePageStatus.Instance}"
            ItemsSource="{Binding Cards}">
            
            <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 HorizontalAlignment="Center" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                        ScrollViewer>
                    Border>
                ControlTemplate>
            ItemsControl.Template>
            
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel Orientation="Horizontal">
                        <WrapPanel.Style>
                            <Style TargetType="WrapPanel">
                                "ItemWidth" Value="420" />
                                "ItemHeight" Value="326.67" />
                            Style>
                        WrapPanel.Style>
                    WrapPanel>
                ItemsPanelTemplate>
            ItemsControl.ItemsPanel>
            
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    
                    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153

    4.模板机制的内部原理

    (1)原理: ItemsControl的默认Template里的ItemsPresenter只起一个占位符(placeholder)的作用,它的主要角色是接收ItemsControl的ItemsPanelTemplate模板,并在ItemsControl应用模板时应用这个模板。这时一个ItemsControl的Template模板里的ItemsPresenter在应用这个ItemsControl的ItemsPanel模板时,会将模板里面的Panel类控件的TemplateParent设定为这个ItemsControl,同时将其IsItemsHost属性标识为true。ItemsControl还有一种用法是忽略ItemsPanel,直接在其Template内指定一个"ItemsPanel",即上述指定IsItemsHost的方式。这时ItemsPanel模板的设置将被直接忽略。不过,这时一定要将这个Panel的IsItemsHost设定为True,否则ItemsControl将找不到一个合适的ItemsPanel来显示列表项。

    (2)总结: 我们再按照从上至下的顺序从整体上梳理一下ItemsControl的模板应用机制即一个ItemsControl在应用模板时,首先会应用Template模板(ControlTemplate类型)生成自身的visual tree(Control类的模板机制),然后Template模板中的ItemsPresenter应用其TemplateParent(即这个ItemsControl)的ItemsPanel模板(ItemsPanelTemplate类型)生成一个visual tree,并把这个visual tree放置在这个ItemsPresenter的位置(ItemsPresenter这时起到占位符的作用)。在ItemsPanel模板被应用时,这个面板的TemplateParent会被指向这个ItemsControl,同时其IsItemsHost属性被标识为true。ItemsControl的ItemContainerGeneror在遍历自己的ItemsInternal列表并为每个列表项(item)生成一个container(ItemContainer),并将ItemsControl的ItemTemplate模板“转交”(forward)给这个container,这样这个container就可以应用模板(ItemTemplate),为与自己对应的数据项(item)生成一个由这个ItemTemplate定义的visual tree。

  • 相关阅读:
    ThinkPHP v6.0.13 存在反序列化漏洞
    BDD - SpecFlow Driver Pattern 驱动模式
    马某 说c# 不开源,他是蠢还是坏?
    力扣(LeetCode)115. 不同的子序列(C++)
    sed去除文件中的引号
    JavaScript高级复习下(60th)
    痞子衡嵌入式:简析i.MXRT1170 MECC64功能特点及其保护片内OCRAM1,2之道
    【超详细Vue2教程】超详细的Vue2入门教程,让你的开发效率大大提高(自己整理的笔记,超详细!)
    系统架构设计师之备考攻略(2022年修订版)——一篇就够
    详解时间复杂度计算公式(附例题细致讲解过程)
  • 原文地址:https://blog.csdn.net/qq_40772692/article/details/126428787