zoukankan      html  css  js  c++  java
  • Template和Style

    简介

    这是一篇记录笔者阅读学习刘铁猛老师的《深入浅出WPF》的读书笔记,如果文中内容阅读不畅,推荐购买正版书籍详细阅读。

    Template 模板的内涵

    WPF系统不但支持传统的Windows Forms编程的用户界面和用户体验设计,更支持使用专门设计工具Microsoft Expreession Blend进行专业设计,更推出了以模板为核心的新一代设计理念

    程序的本质是算法和数据结构,WPF中作为一种“形式”,它要表现的“内容”就是算法和数据结构,Binding传递的是数据,事件参数携带的也是数据;方法和委托调用的是算法,事件传递消息也是算法······,作为“表现形式”,每个控件都是为了实现某种用户操作算法和直观显示某种数据而生,一个控件看上去是什么样子由它的“算法内容”和“数据内容”决定,这就是内容决定形式

    • 控件的“算法内容”:指控件能展示哪些数据、具有哪些方法、能响应那些操作、能激发什么事件、简而言之就是控件的功能,它们是一组相关的算法逻辑。
    • 控件的“数据内容”:控件所展示的具体数据是什么。

    以往的GUI开发技术(Windows Forms)耦合度过高,控件内部的逻辑和数据是固定的,程序员无法改变,外观可以操作的空间也较少,造成这个局面的根本原因就是数据和算法的”形式“和”内容“耦合度太紧了。

    在WPF中,通过引入Template(模板)将数据和算法的”内容“与“形式”解耦了。WPF中的Template分为两大类:

    • ControlTemplate 是算法内容的表现形式,一个控件怎样组织其内部结构才能让它更符合业务逻辑,让用户操作起来更舒服就是由它来控制的。它决定了控件”长什么样子“,并让程序员有机会在控件原有的内部逻辑基础上扩展自己的逻辑。
    • DataTemplate 是数据内容的表现形式,一条数据显示成什么样子,是简单的文本还是直观的图形动画就是由它来决定。

    一言蔽之,Template就是”外衣“——ControlTemplate是控件的外衣,DataTemplate是数据的外衣。

    DataTemplate 数据外衣-数据内容的表现形式

    DataTemplate常用的地方有3处,分别是:

    • ContentControl 的ContentTemplate 属性,相当于给ContentControl的内容穿衣服。
    • ItemsControl 的ItemTemplate属性,相当于给ItemsControl 的数据条目传衣服。
    • GridViewColumn的CellTemplate属性,相当于给GridViewColumn单元格里的数据穿衣服。

    示例:

    需求:有一列汽车数据,这里数据显示在一个ListBox里,要求ListBox的条目显示汽车的厂商标志和简要参数,单击某个条目后在窗口的详细内容区域显示汽车的照片和详细参数。

    1. 添加资源文件夹引入对应汽车图标
    2. 创建详细内容窗口的DataTemplate
    3. 创建ListBox的DataTemplate
    4. 使用对应模板
    5. Binding对应数据
    <Window x:Class="Template.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:Template"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
        <Window.Resources>
            <!--Converters-->
            <!--创建详细视图的数据模板-->
            <DataTemplate x:Key="carDetailViewTemplate">
                <Border BorderBrush="Black" BorderThickness="1" CornerRadius="6">
                    <StackPanel Margin="5">
                        <Image Width="400" Height="250"
                               Source="G:VsProjectWPF练习TemplateResourcesAodi.jpg"/>
                        <StackPanel Orientation="Horizontal" Margin="5">
                            <TextBlock Text="Name:" FontWeight="Bold" FontSize="20"/>
                            <TextBlock Text="{Binding Name}" FontSize="20" Margin="5,0"/>
                        </StackPanel>
                        <StackPanel Orientation="Horizontal" Margin="5,0">
                            <TextBlock Text="Automaker:" FontWeight="Bold"/>
                            <TextBlock Text="{Binding Automaker}" Margin="5,0"/>
                            <TextBlock Text="Year:" FontWeight="Bold"/>
                            <TextBlock Text="{Binding Year}" Margin="5,0"/>
                            <TextBlock Text="Top Speed:" FontWeight="Bold"/>
                            <TextBlock Text="{Binding TopSpeed}" Margin="5,0"/>
                        </StackPanel>
                    </StackPanel>
                </Border>
            </DataTemplate>
            <!--创建ListBox条目的数据模板-->
            <DataTemplate x:Key="carListItemViewTemplate">
                <Grid Margin="2">
                    <StackPanel Orientation="Horizontal">
                        <Image Grid.RowSpan="3" Width="64" Height="64"
                               Source="G:VsProjectWPF练习TemplateResourcesAodi.png"/>
                        <StackPanel Margin="5,0">
                            <TextBlock Text="{Binding Name}" FontSize="16" FontWeight="Bold"/>
                            <TextBlock Text="{Binding Year}" FontSize="14"/>
                        </StackPanel>
                    </StackPanel>
                </Grid>
            </DataTemplate>
        </Window.Resources>
        <!--窗体内容 使用对应模板-->
        <Grid>
            <UserControl ContentTemplate="{StaticResource carDetailViewTemplate}"
                         Content="{Binding SelectedItem,ElementName=listBoxCars}"/>
            <ListBox x:Name="listBoxCars" Width="180" Margin="607,0,13,0"
                     ItemTemplate="{StaticResource carListItemViewTemplate}"/>
        </Grid>
    </Window>
    
    
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            InitialCarList();
        }
    
        private void InitialCarList()
        {
            List<Car> carList = new List<Car>()
            {
                new Car(){Automaker = "Lamborghini",Name = "Diablo", Year = "1990",TopSpeed = "340"},
                new Car(){Automaker = "Lamborghini",Name = "Murcielago",Year = "2001",TopSpeed="353"},
                new Car(){Automaker = "Lamborghini",Name="Callardo",Year="2003",TopSpeed="325"},
                new Car(){Automaker="Lamborghini",Name="Reventon",Year="2008",TopSpeed="356"},
            };
            this.listBoxCars.ItemsSource = carList;
        }
    }
    
    

    ControlTemplate 控件的外衣-算法内容的表现形式

    ControlTemplate的两个作用:

    • 通过更换ControlTemplate改变控件外观,使之具有更优的用户使用体验及外观。
    • 借助ControlTemplate,程序员与设计师可以并行工作,程序员可以先用WPF标准控件进行编程,等设计师的工作完成后,只需把新的ControlTemplate应用到程序中就可以了。

    示例:

    1. 文档大纲-》选中需要设计的控件-》右键编辑模板-编辑副本-》设置名称和位置
    2. 修改设计需要的模板ControTemplate,TemplateBinding将控件模板中的属性值关联到目标控件上,产生的效果就是你为目标控件设置的值以后,控件模板的值也会随之改变。
    3. 目标控件使用对应的模板,Style="{DynamicResource RoundCornerTexBoxStyle}

    TemplateBinding是为了某个特定场景优化出来的数据绑定版本--需要把ControlTemplate里面的某个Property绑定到应用该ControlTemplate的控件的对应Property上。
    中文表达比较拗口,MSDN的原文“Links the value of a property in a control template to be the value of a property on the templated control.”

    翻译:将控件模板中属性的值链接为模板化控件上的属性的值

    ItemsControl的PanelTemplate

    ItemsControl具有一个名为ItemsPanel的属性,它的数据类型为ItemsPanelTemplate,也是一种控件Template,可以控制ItemsControl的条目容器。

    示例:制作一个横向排列的ListBox

    <ListBox>
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
        <TextBlock Text="菜单"/>
        <TextBlock Text="帮助"/>
        <TextBlock Text="请求"/>
        <TextBlock Text="张三"/>
    </ListBox>
    

    DataTemplate与ControlTemplate的关系与应用

    DataTemplate与ControlTemplate的关系

    控件只是数据和行为的载体、是个抽象的概念,至于它本身长什么样子(控件的内部结构)、它的数据会长成什么样子(数据显示结构)都是靠Template生成的。

    • ControlTemplate决定控件的外观,生成的控件树的树根是ControlTemplate的目标控件,此模块化控件的Template属性值就是这个ControlTemplate实例。
    • DataTemplate决定数据外观,生成的控件树的树根是一个ContentPresenter控件,此模块化控件的ContentTemplate属性值就是这个DataTemplate示例。

    因为ContentPresenter控件是ControlTemplate控件树上的一个节点,所以DataTemplate控件树是ControlTemplate控件树的一棵子树。

    DataTemplate与ControlTemplate的应用

    为Template设置其应用目标有两种方法:

    1. 逐个设置控件的Template、ContentTemplate、ItemsTemplate、CellTemplate等属性,不想应用Template的控件不设置。
    2. 把Template应用在某个类型的控件或数据上。

    使用方法

    1. 把ControlTemplate应用在所有目标上需要借助Style来实现,但Style不能标记x:Key。Style没有x:Key标记,默认为应用到所有由x:Type指定的控件上,如果不想应用则需把控件的Style标记为{x:Null}.

    2. 把DataTemplate应用在某个数据类型上的方法是设置DataTemplate的DataType属性,并且DataTemplate作为资源时也不能带有x:Key标记。DataTemplate具有直接把XML数据节点当作目标对象的功能——XML数据中的元素名(标签名)可以作为DataType,元素的子节点可以使用XPath来访问

      • HierarchicalDataTemplate层级数据模板能够帮助层级控件显示数据,例如:TreeView,MenuItem控件。

    DataTemplate示例:

    <Window.Resources>
            <DataTemplate DataType="{x:Type local:Unit}">
                <Grid>
                    <StackPanel Orientation="Horizontal">
                        <Grid>
                            <Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding Price}"/>
                            <TextBlock Text="{Binding Year}"/>
                        </Grid>
                        <TextBlock Text="{Binding Price}" Margin="5"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>
            <!--数据源-->
            <c:ArrayList x:Key="ds">
                <local:Unit Year="2001年" Price="100"/>
                <local:Unit Year="2002年" Price="120"/>
                <local:Unit Year="2001年" Price="100"/>
                <local:Unit Year="2002年" Price="120"/>
                <local:Unit Year="2001年" Price="100"/>
            </c:ArrayList>
        </Window.Resources>
    <Grid>
            <StackPanel>
                <ListBox ItemsSource="{StaticResource ds}"/>
                <ComboBox ItemsSource="{StaticResource ds}"/>
            </StackPanel>
        </Grid>
    //C#代码 
     public class Unit
     {
         public int Price { get; set; }
         public string Year { get; set; }
     }
    <!--使用XPath访问元素的子节点-->
    <Window.Resources>
            <DataTemplate DataType="Unit">
                <Grid>
                    <StackPanel Orientation="Horizontal">
                        <Grid>
                            <Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding XPath=@Price}"/>
                            <TextBlock Text="{Binding XPath=@Year}"/>
                        </Grid>
                        <TextBlock Text="{Binding XPath=@Price}" Margin="5"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>
            <!--数据源-->
            <XmlDataProvider x:Key="ds" XPath="Units/Unit">
                <x:XData>
                    <Units xmlns="">
                        <Unit Year="2001年" Price="100"/>
                        <Unit Year="2002年" Price="120"/>
                        <Unit Year="2001年" Price="100"/>
                        <Unit Year="2002年" Price="120"/>
                        <Unit Year="2001年" Price="100"/>
                    </Units>
                </x:XData>
            </XmlDataProvider>
    </Window.Resources>
    <Grid>
            <StackPanel>
                <ListBox ItemsSource="{Binding Source={StaticResource ds}}"/>
                <ComboBox ItemsSource="{Binding Source={StaticResource ds}}"/>
            </StackPanel>
    </Grid>
    

    HierarchicalDataTemplate示例:

    <!--TreeView示例-->
    <Window.Resources>
            <!--数据源-->
            <XmlDataProvider x:Key="ds" Source="G:VsProjectWPF练习TreeViewData.xml" XPath="Data/Grade"/>
            <!--年级模板-->
            <HierarchicalDataTemplate DataType="Grade" ItemsSource="{Binding XPath=Class}">
                <TextBlock Text="{Binding XPath=@Name}"/>
            </HierarchicalDataTemplate>
            <!--班级模板-->
            <HierarchicalDataTemplate DataType="Class" ItemsSource="{Binding XPath=Group}">
                <RadioButton Content="{Binding XPath=@Name}" GroupName="gn"/>
            </HierarchicalDataTemplate>
            <!--小组模板-->
            <HierarchicalDataTemplate DataType="Group" ItemsSource="{Binding XPath=Student}">
                <CheckBox Content="{Binding XPath=@Name}"/>
            </HierarchicalDataTemplate>
    </Window.Resources>
    <Grid>
            <TreeView Margin="5" ItemsSource="{Binding Source={StaticResource ds}}"/>
    </Grid>
    <!--TreeView示例-->
    <Window.Resources>
            <!--数据源-->
            <XmlDataProvider x:Key="ds" Source="G:VsProjectWPF练习MenuData.xml" XPath="Data/Operation"/>
            <!--Operation模板-->
            <HierarchicalDataTemplate DataType="Operation"
                                      ItemsSource="{Binding XPath=Operation}">
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding XPath=@Name}" Margin="10,0"/>
                    <TextBlock Text="{Binding XPath=@Gesture}"/>
                </StackPanel>
            </HierarchicalDataTemplate>
    </Window.Resources>
    <Grid>
            <StackPanel>
                <Menu ItemsSource="{Binding Source={StaticResource ds}}"/>
            </StackPanel>
    </Grid>
    

    Style样式

    Style简单来说,就是一种对属性值的批处理,类似于Html的CSS,可以快速的设置一系列属性值到UI元素。

    Style最重要的两个元素是Setter和Trigger,Setter类设置控件的静态外观风格,Trigger类设置控件的行为风格。

    Style和Template就如同化妆和整容,Style可以为某类控件设置统一的样式,如果不想使用该样式使用{x:Null}就可清空Style.

    Setter设置器

    Setter设置器的两个重要元素是Property和Value,Property属性用来指明你想为那个目标的那个属性赋值;Value属性则是你提供的属性值。

    <Window.Resources>
            <Style TargetType="TextBlock">
                <Setter Property="FontSize" Value="24"/>
                <Setter Property="TextDecorations" Value="Underline"/>
                <Setter Property="FontStyle" Value="Italic"/>
            </Style>
    </Window.Resources>
    <StackPanel Margin="5">
            <TextBlock Text="你好"/>
            <TextBlock Text="这是设置好的样式"/>
            <TextBlock Text="没有风格" Style="{x:Null}"/>
    </StackPanel>
    

    Trigger触发器

    Trigger,触发器,即当某些条件满足时会触发一个行为(比如某些值的变化或动画的发生等)。触发器比较像事件。事件一般是由用户操作触发的,而触发器除了由事件触发的EventTrigger外还有数据变化触发型的Trigger、DataTrigger及多条件触发型的MultiTrigger、MultiDataTrigger等。

    基本Trigger

    Trigger类是最基本的触发器,类似于Setter,Trigger也有Property和Value这两个属性,Property是Trigger关注的属性名称,Value是触发条件。Trigger还有一个Setters属性,此属性值是一组Setter,一旦触发条件被满足,这组Seteer的“属性-值”就会被应用,触发条件不再满足后,各属性值会被还原。

    示例:

    CheckBox的Style,当IsChecked属性为true时,前景色和字体变化。

    <Window.Resources>
            <Style TargetType="CheckBox">
                <Style.Triggers>
                    <Trigger Property="IsChecked" Value="True">
                        <Trigger.Setters>
                            <Setter Property="FontSize" Value="20"/>
                            <Setter Property="Foreground" Value="Orange"/>
                        </Trigger.Setters>
                    </Trigger>
                </Style.Triggers>
            </Style>
    </Window.Resources>
    <Grid>
            <StackPanel>
                <CheckBox Content="悄悄的我走了" Margin="5"/>
                <CheckBox Content="悄悄的我走了" Margin="5"/>
                <CheckBox Content="悄悄的我走了" Margin="5"/>
                <CheckBox Content="悄悄的我走了" Margin="5"/>
                <CheckBox Content="悄悄的我走了" Margin="5"/>
            </StackPanel>
    </Grid>
    

    MultiTrigger

    MultiTrigger必须多个条件同时成立才会被触发,MultiTrigger比Trigger多了一个Conditions属性,需要同时成立的条件就存储在这个集合中。

    示例:同时满足CheckBox被选中且选中为“吃饭”时才会被触发。

    <Window.Resources>
            <Style TargetType="CheckBox">
                <Style.Triggers>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsChecked" Value="true" />
                            <Condition Property="Content" Value="吃饭"/>
                        </MultiTrigger.Conditions>
                        <MultiTrigger.Setters>
                            <Setter Property="FontSize" Value="20"/>
                            <Setter Property="Foreground" Value="Orange"/>
                        </MultiTrigger.Setters>
                    </MultiTrigger>
                </Style.Triggers>
            </Style>
    </Window.Resources>
    <Grid>
            <StackPanel>
                <CheckBox Content="吃饭"/>
                <CheckBox Content="睡觉"/>
                <CheckBox Content="打豆豆"/>
                <CheckBox Content="用四川话说"/>
            </StackPanel>
    </Grid>
    

    由数据触发DataTrigger

    DataTrigger,基于数据执行某些判断,DataTrigger对象的Binding属性会源源不断送过来,一旦送过来的值与Value属性一致,DataTrigger即被触发。

    示例:当TextBox的Text长度小于7个字符时其Border会保持红色

    <Window.Resources>
            <local:L2BConverter x:Key="cvtr"/>
            <Style TargetType="TextBox">
                <Style.Triggers>
                    <DataTrigger Value="false"
                        Binding="{Binding RelativeSource={x:Static RelativeSource.Self},Path=Text.Length,Converter={StaticResource cvtr}}">
                        <Setter Property="BorderBrush" Value="Red"/>
                        <Setter Property="BorderThickness" Value="1"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
    </Window.Resources>
    <StackPanel>
            <TextBox Margin="5"/>
            <TextBox Margin="5,0"/>
            <TextBox Margin="5"/>
    </StackPanel>
    

    这个例子中唯一需要解释的就是DataTrigger的Binding,为了将自己作为数据源,使用了RelativeSource,初学者经常认为“不明确指出Source的值Binding就会将控件自己作为数据的来源”,这是错误的,因为不明确指出Source时Binding会把控件的DataContext属性当作数据源而非把控件自身当作数据源。Binding的Path被设置为Text.Lenght,即我们关注的是字符串的长度,长度是一个具体的数字,如何基于这个长度值做判断呢?这就用到了Converter。我们创建如下的Converter:

    class L2BConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                int textLenght = (int)value;
                return textLenght > 6 ? true : false;
            }
            public object ConvertBack(object value,Type targetType,object parmeter,CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    

    多条数据条件触发的MultiDataTrigger

    示例:用户界面上使用ListBox显示了一列Student数据,当Student对象同时满足ID为2、Name为张三的时候,条目就会高亮显示。

    <Window.Resources>
            <Style TargetType="ListBoxItem">
                <Setter Property="ContentTemplate">
                    <Setter.Value>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Text="{Binding ID}" Width="60"/>
                                <TextBlock Text="{Binding Name}" Width="120"/>
                                <TextBlock Text="{Binding Age}" Width="60"/>
                            </StackPanel>
                        </DataTemplate>
                    </Setter.Value>
                </Setter>
                <Style.Triggers>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding Path=ID}" Value="2"/>
                            <Condition Binding="{Binding Path=Name}" Value="张三"/>
                        </MultiDataTrigger.Conditions>
                        <MultiDataTrigger.Setters>
                            <Setter Property="Background" Value="Orange"/>
                        </MultiDataTrigger.Setters>
                    </MultiDataTrigger>
                </Style.Triggers>
            </Style>
    </Window.Resources>
    <StackPanel>
            <ListBox x:Name="listBoxStudent" Margin="5"/>
    </StackPanel>
    
    public class Student
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public List<Student> Students { get; set; }
    }
    
    public MainWindow()
    {
        InitializeComponent();
        Student st = new Student();
        List<Student> students = new List<Student>();
        students.Add(new Student() { ID = 1, Name = "张三", Age = 20 });
        students.Add(new Student() { ID = 2, Name = "张三", Age = 20 });
        students.Add(new Student() { ID = 3, Name = "张三", Age = 20 });
        listBoxStudent.Items.Clear();
        listBoxStudent.ItemsSource = students;
    }
    

    由事件触发的EventTrigger

    EventTrigger是触发器中最特殊的一个。首先,它不是由属性值或数据的变化来触发而是由事件来触发;其次被触发后它并非应用一组Setter,而是执行一段动画。因此UI层的动画效果往往与EventTrigger相关联。

    示例:创建一个针对Button的Style,这个Style包含两个EventTrigger,一个由MouseEnter事件触发,另外一个由MouseLeave事件触发。

    <Window.Resources>
            <Style TargetType="Button">
                <Style.Triggers>
                    <EventTrigger RoutedEvent="MouseEnter">
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Width"/>
                                <DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Height"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                    <EventTrigger RoutedEvent="MouseLeave">
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Width"/>
                                <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Height"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </Style.Triggers>
            </Style>
    </Window.Resources>
    <Canvas>
            <Button Width="40" Height="40" Content="OK"/>
    </Canvas>
    
    登峰造极的成就源于自律
  • 相关阅读:
    4单元练习
    3单元C#练习(重复太多,差别着写)
    ===习题
    .NET预习
    NET_.NET深入体验与实践精要----第四章
    NET 第一章
    C#认证考试试题汇编 Test
    _.NET深入体验与实战精要.pdf第四章
    C# 练习
    .NET 学习
  • 原文地址:https://www.cnblogs.com/fishpond816/p/13590591.html
Copyright © 2011-2022 走看看