zoukankan      html  css  js  c++  java
  • WPF 之 ControlTemplate 和 DataTemplate(十)

    一、前言

    ​ 控件(Control)是数据内容表现形式和算法内容表现形式的双重载体。控件的数据内容表现形式让用户可以直观的看到数据,算法内容形式可以让用户方便的操作逻辑。作为“表现形式”,每个控件都是为了实现用户某种操作算法和直观展示某种数据而生。即控件由“算法内容”和“数据内容”所决定(内容决定形式):

    • 控件的“算法内容”:即控件能够实现的功能,它们是一组算法逻辑。
    • 控件的“数据内容”:即控件所展示的具体内容是什么。

    ​ 以往的 GUI 开发技术(如MFC、 Windows Forms 和 ASP.NET)中,控件内部的逻辑和数据是固定的,控件的外观更改必须通过控件属性去更改,无法改变控件内部结构。如果要扩展一个控件的功能,则必须创建控件的子类或用户控件。造成这个问题的根本原因是算法内容和数据内容耦合的太过紧密。

    ​ 为了解决以上问题,WPF t推出了模板(Template)。WPF 的 Template 分为两大类:

    • ControlTemplate:算法内容的表现形式,控制控件内部结构更符合业务逻辑、更方便用户操作。它决定了控件“长的样子”。
    • DataTemplate:内容数据的表现形式,决定一条数据展现成什么样子。

    ​ 即 Template 就是“外衣”—— ControlTemplate 是控件的“外衣”,“DataTemplate” 是数据的外衣

    二、数据的外衣—— DataTemplate

    DataTemplate 常用的地方有三处:

    1. ContentControl 的 ContentTemplate 属性:相当于给 ContentCtrol 的内容穿外衣。
    2. ItemsControl 的 ItemsTemplate 属性:相当于给 ItemsControl 的数据条目穿外衣。
    3. GridViewColumn 的 CellTemplate 属性:相当于给 GridViewColumn 的单元格里的数据穿外衣。

    例如,我们实现如下功能:

    image-20210221154921483

    我们先定义一个数据模型:

    public class Unit
        {
            public string Year { get; set; }
            public uint Price { get; set; }
        }
    }
    

    然后定义 DataTemplate,并将该 DataTemplate 绑定到ListBox上,具体如下:

         <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}年}"></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>
    
    

    三、控件的外衣—— ControlTemplate

    ControlTemplate 的作用:

    • 通过更换 ControlTemplate 改变控件外观,使之拥有更优的用户体验
    • 设计师与程序员可以并行工作,程序员先完成开发工作,等设计师完成 ControlTemplate 后更换即可。

    需要注意的是编辑 ControlTemplate ,但实际是把 ControlTemplate 包含在 Style里面。例如,我们设置一个圆角 TextBox 和圆角 Button,如下:

    <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>
    

    注意:Logical Tree 和 Visual Tree 的交点即是 ControlTemplate。

    Style 包含 Setter 和 Trigger。Setter 设置控件的静态外观风格,即控件的属性设置器。Trigger 设置控件的行为风格,行为风格是由对外界刺激的响应体现出来的。

    Setter,为属性设置器,采用“属性名=属性值”的方式进行属性。

    Trigger :基本触发器,类似于 Setter , property 是 Trigger 关注的属性名,Value 是触发条件。

    Setter 和 Trigger 使用如下,我们实现圆角按钮并实现当鼠标移动至按钮上方时,按钮变为灰色功能:

        <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:必须多个条件同时成立时才会被触发。具体实例如下,当 CheckBox 选中,且内容为“Test”时,才触发字体显示灰色且放大的功能:

          <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:基于数据执行某些判断情况。具体实例如下,当输入 TextBox 文本内容的长度小于3时,TextBox 的边框会是红色:

        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:要求多个数据条件同时满足才能触发。具体实例如下,使用 ListBox 显示一列 Student 数据,当 Student 的 Name=“dwayne” 且 Age=10 时,该列高亮:

           <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:为触发器中最特殊的一个,首先它由事件触发,其次触发的是一组动画,而非 Setter。具体实例如下,鼠标进入 Button 界面后,按钮放大字体放大,离开后恢复:

     <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>
    

    四、DataTemplate 与 ControlTemplate 的关系与应用

    凡是 Template ,都是最终作用到控件上,这个控件就是 Template 的目标控件,也叫模板化控件(Template Control)。

    DataTemplate 的目标是数据,但展示数据需要载体,这个载体一般是 ContentPresenter 对象上,而 ContentPresenter 对象只有ContentTemplate 而没有 Template 属性,这就说明 ContentPresener 是一组专门用于承载 DataTemplate 的控件。具体关系如下所示:

    image-20210222102631231

    即由 ControlTemplate 生成的控制树,其树根是 ControlTemplate 的目标控件,目标控件的 Template 属性值就是 ControlTemplate 的实例。而 DataTmeplate 生成的控制树,其树根是 ContentPresenter 控件,ControlPresenter 控件的 ContentTemplate 属性值就是 DataTemplate 的实例。

  • 相关阅读:
    linux软件安装方式
    docker 安装 jenkins touch: cannot touch ‘/var/jenkins_home/copy_reference_file.log’: Permission denied Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?
    [ERR] Node goodsleep.vip:6379 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.
    Linux 常用命令 服务器间scp 用户 export 创建文件、软连接
    redis 安装 集群 主从 哨兵 docker
    WPF密码框中禁止复制、粘贴
    Application 统计在线人数
    【转义字符】HTML 字符实体&lt; &gt: &amp;等
    SQL语句统计每天的数据
    正则表达式计算代码数
  • 原文地址:https://www.cnblogs.com/dongweian/p/14428889.html
Copyright © 2011-2022 走看看