WPF 模板总结
模板(Template): WPF系统不但支持传统的Winfrom编程的用户界面和用户体验设计,更支持使用专门的设计工具Blend进行专业设计,同时还推出了以模板为核心的新一代设计理念。
在WPF中,通过引入模板(Template)微软将数据和算法的“内容”与“形式”解耦了。模板是算法和数据的外衣,决定了它们长什么样子。 WPF中的模板(Template)分为两大类: ControlTemplate是算法内容的表现形式,一个控件怎样组织其内部结构才能更符合业务逻辑和需求。 DataTemplate是数据内容表现形式,决定一条数据显示成什么样子。 Style(样式):设置控件样式,构成Style重要的两个元素是Setter和Trigger,Setter类帮助我们设置控件的静态外观风格,Trigger类则帮助我们设置控件的行为风格。 Template与Style联系和区别:如果只需对控件进行小幅度修饰(调整大小、位置、字体、颜色等)就用style,如果需要改变控件的外观和行为就用controlTemplate(形状、事件触发如鼠标停留效果等)。
在实际项目中,经常把Template定义在Style中,通过Style 中的Property来设置控件的Template属性。 ControlTemplate 控件模板主要有两个重要属性:VisualTree内容属性和Triggers触发器。所谓VisualTree(视觉树),就是呈现我们所画的控件。Triggers可以对我们的视觉树上的元素进行一些变化。
一般用于单内容控件。如设置一个圆角Button的ControlTemplate实例如下:
Note:
Style设置Key值,控件引用其Key来设置自身样式;如果Style没有设置Key值,则Style作用域内所有TargetType类型控件都默认使用其样式。
ControlPresenter 通常叫做内容占位符,用来替换ContentControl控件。如果没有ControlPresenter ,内容控件就没有内容显示(Button上的字将不显示)。
ItemsPresenter用于显示条目数据,作为条目内容占位符。
DataTemplate
允许定制.NET对象的外观,也就是数据的外观,常用在以下3处:ContentControl的ContentTemplate属性,用于定制ContentControl内容的外观;
ItemsControl的ItemTemplate属性,用于定制ItmsControl数据条目的外观;GridViewColumn的CellTemplate属性,相当于定制GridViewColumn单元格数据的外观。
1.ContentTemplate使用: <Window x:Class="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:MainWindow" mc:Ignorable="d" Title="MainWindow" Height="150" Width="360"> <Window.Resources> <DataTemplate x:Key="template1"> <TextBlock Text="{Binding}" FontSize="12" FontWeight="Bold" TextWrapping="Wrap"></TextBlock> </DataTemplate> </Window.Resources> <Grid> <ContentControl Name="contCtrl" ContentTemplate="{StaticResource template1}" Content="This is the content of the content control."/> </Grid> </Window>
2.ItemTemplate使用: 后端代码: using System; using System.Collections.Generic; using System.Windows; namespace WpfTemplate { /// <summary> /// DataTemplateDemo.xaml 的交互逻辑 /// </summary> public partial class DataTemplateDemo : Window { public List<Book> BookList { get; set; } = new List<Book>(); public DataTemplateDemo() { InitializeComponent(); BookList.Add(new Book() { Title = "三国演义", Author = "罗贯中",Time=DateTime.Now.AddYears(-200) }); BookList.Add(new Book() { Title = "红楼梦", Author = "曹雪芹", Time = DateTime.Now.AddYears(-150) }); BookList.Add(new Book() { Title = "西游记", Author = "吴承恩", Time = DateTime.Now.AddYears(-230) }); } } }
前端代码: <Window x:Class="WpfTemplate.DataTemplateDemo" 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:WpfTemplate" mc:Ignorable="d" Title="DataTemplateDemo" Name="win" Height="400" Width="600"> <Window.Resources> <DataTemplate x:Key="MyDataTemplate"> <StackPanel Orientation="Horizontal"> <Border Background="Pink"> <TextBlock Text="{Binding Title}"/> </Border> <Button Content="{Binding Author}" Cursor="Hand" Margin="10,0"/> </StackPanel> </DataTemplate> </Window.Resources> <Grid> <ListBox Grid.Row="1" ItemsSource="{Binding BookList,ElementName=win}" ItemTemplate="{StaticResource MyDataTemplate}"/> </Grid> </Window>
3.CellTemplate使用 <Window x:Class="WpfTemplate.DataTemplateDemo" 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:WpfTemplate" mc:Ignorable="d" Title="DataTemplateDemo" Name="win" Height="400" Width="600"> <Grid> <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding BookList,ElementName=win}" Grid.Row="1" Grid.Column="1"> <DataGrid.Columns> <DataGridTextColumn Header="书名" Binding="{Binding Title}" /> <DataGridTextColumn Header="作者" Binding="{Binding Author}" /> <DataGridTemplateColumn Header="时间"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <DatePicker SelectedDate="{Binding Time}" BorderThickness="0" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> </Grid> </Window>
ItemsPanelTemplate使用: ItemsPanelTemplate可以被设置为ItemsControl的ItemsPanel。例中,ItemsControl的条目显示默认是垂直排列,通过ItemsPanel属性修改为水平排列。 <Grid> <ListBox> <!--ItemsPanel--> <ListBox.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </ListBox.ItemsPanel> <!--条目--> <TextBlock Text="Allan"/> <TextBlock Text="Kevin"/> <TextBlock Text="Drew"/> <TextBlock Text="Timothy"/> </ListBox> </Grid>
Template扩展: 很多时候数据是以XML形式存储的,DataTemplate具有直接把XML数据结点当作目标对象的功能——XML数据中的元素名(标签名)可以作为DataType,元素的子结点和Attribute可以使用XPath来访问。下面的代码使用XmlDataProvider作为数据源,代码如下: <Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApp" xmlns:c="clr-namespace:System.Collections;assembly=mscorlib" Title="MainWindow" Height="500" Width="333.035"> <Window.Resources> <!--Data Template--> <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.0"/> </StackPanel> </Grid> </DataTemplate> <!--数据源--> <XmlDataProvider x:Key="ds" XPath="Units/Unit"> <x:XData> <Units xmlns=""> <Unit Year="2001" Price="100"/> <Unit Year="2001" Price="120"/> <Unit Year="2001" Price="140"/> <Unit Year="2001" Price="160"/> <Unit Year="2001" Price="180"/> <Unit Year="2001" Price="200"/> </Units> </x:XData> </XmlDataProvider> </Window.Resources> <StackPanel> <ListBox ItemsSource="{Binding Source={StaticResource ds}}"/> <ComboBox ItemsSource="{Binding Source={StaticResource ds}}" Margin="5"/> </StackPanel> </Window>
显示层级数据的模板HierarchicalDataTemplate XML最大的优势是可以方便地表示带有层级的数据,WPF准备了TreeView和Menultem控件用来显示层级数据,能够帮助层级控件显示层级数据的模板是HierarchicalDataTemplate。第一个例子是使用TreeView显示多层级、不同类型数据,需要为每种数据设计一个模板,有机会使每种数据类型有自己独特的外观。数据保存在项目根目录的Data.xml文件中,内容如下: <?xml version="1.0" encoding="utf-8" ?> <Data xmlns=""> <Grade Name="一年级"> <Class Name="甲班"> <Group Name="A组"/> <Group Name="B组"/> <Group Name="C组"/> </Class> <Class Name="乙班"> <Group Name="A组"/> <Group Name="B组"/> <Group Name="C组"/> </Class> </Grade> <Grade Name="二年级"> <Class Name="甲班"> <Group Name="A组"/> <Group Name="B组"/> <Group Name="C组"/> </Class> <Class Name="乙班"> <Group Name="A组"/> <Group Name="B组"/> <Group Name="C组"/> </Class> </Grade> </Data>
程序的XAML代码如下: <Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="400" Width="333.035"> <Window.Resources> <!--数据源--> <XmlDataProvider x:Key="ds" Source="Data.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> </Window>
第二个例子是同一种数据类型的嵌套结构,这种情况下只需设计一个HierarchicalDataTemplate,它会产生自动迭代应用的效果。数据仍然存放在Data.xml文件中,数据全都是Operation类型: <?xml version="1.0" encoding="utf-8" ?> <Data xmlns=""> <Operation Name="文件" Gesture="F"> <Operation Name="新建" Gesture="N"> <Operation Name="项目" Gesture="Control+P"/> <Operation Name="网站" Gesture="Control+W"/> <Operation Name="文档" Gesture="Control+D"/> </Operation> <Operation Name="保存" Gesture="S"/> <Operation Name="打印" Gesture="P"/> <Operation Name="退出" Gesture="X"/> </Operation> <Operation Name="编辑" Gesture="E"> <Operation Name="拷贝" Gesture="Control+C"/> <Operation Name="剪切" Gesture="Control+X"/> <Operation Name="粘贴" Gesture="Control+V"/> </Operation> </Data>
程序的XAML代码如下: <Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" Title="MainWindow" Height="300" Width="300"> <Window.Resources> <!--数据源--> <XmlDataProvider x:Key="ds" Source="Data.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> <StackPanel MenuItem.Click="StackPanel_Click"> <Menu ItemsSource="{Binding Source={StaticResource ds}}"/> </StackPanel> </Window>
HierarchicalDataTemplate的作用目标是Menultem的Header,可以从被单击Menultem的Header中取出XML数据。事件处理器代码如下: private void StackPanel_Click(object sender, RoutedEventArgs e) { MenuItem mi = e.OriginalSource as MenuItem; XmlElement xe = mi.Header as XmlElement; MessageBox.Show(xe.Attributes["Name"].Value); }
Note:可以维护一个CommandHelper类,根据拿到的数据来决定执行什么RoutedCommand。 从外界访问Template内部的控件及其属性值 由ControlTemplate或DataTemplate生成的控件都是“由Template生成的控件”,ControlTemplate和DataTemplate两个类均派生自FrameworkTemplate类,有个名为FindName的方法可以检索其内部控件。 检索ControlTemplate生成的控件 设计一个ControlTemplate并把它应用在一个UserControl上,界面上还有一个Button,在它的Click事件处理器中检索由ControlTemplate生成的代码。 程序的XAML代码如下: <Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="160" Width="300"> <Window.Resources> <ControlTemplate x:Key="cTmp"> <StackPanel Background="Orange"> <TextBox x:Name="textBox1" Margin="6"/> <TextBox x:Name="textBox2" Margin="6,0"/> <TextBox x:Name="textBox3" Margin="6"/> </StackPanel> </ControlTemplate> </Window.Resources> <StackPanel Background="Yellow"> <UserControl x:Name="uc" Template="{ StaticResource cTmp}" Margin="5"/> <Button Content="Find by Name" Width="120" Height="30" Click="Button_Click"/> </StackPanel> </Window> 后台代码如下: private void Button_Click(object sender, RoutedEventArgs e) { TextBox tb = this.uc.Template.FindName("textBox1", this.uc) as TextBox; tb.Text = "Hello WPF"; StackPanel sp = tb.Parent as StackPanel; (sp.Children[1] as TextBox).Text = "Hello ControlTemplate,I can find you!"; }
Trigger扩展: 由数据触发的DataTrigger 基于数据执行某些判断可以考虑使用DataTrigger,DataTrigger对象的Binding属性会把数据源源不断送过来,一旦送来的值与Value属性一致DataTrigger即被触发。 下面例子中,当TextBox的Text长度小于7个字符时其Border会保持红色,XAML代码如下: <Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApp" Title="MainWindow" Height="123.435" Width="204.297"> <Window.Resources> <local:L2BConverter x:Key="cvtr"/> <Style TargetType="TextBox"> <Style.Triggers> <DataTrigger Binding="{ Binding RelativeSource={x:Static RelativeSource.Self}, Path=Text.Length, Converter={ StaticResource cvtr}}" Value="false"> <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>
为了将控件自己作为数据源需要使用RelativeSource,如果不明确指出Source时Binding会把控件的DataContext属性当作数据源而非把控件自身当作数据源。 Binding的Path被设置为Text.Length,字符串的长度是一个具体的数字,基于这个长度值做判断时需要用到Converter,创建如下的Converter: //经Converter转换后,长度值会转换成bool类型值,DataTrigger的Value被设置为false。 public class L2BConverter : IValueConverter { public object Convert(object value,Type targetype,object parameter, CultureInfo culture) { int textLength = (int)value; return textLength > 6 ? true : false; } public object ConvertBack(object value, Type targetype, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
多数据条件触发的MultiDataTrigger 遇到要求多个数据条件同时满足时才能触发变化的需求,此时可以考虑使用MultiDataTrigger。 用户界面上使用ListBox显示了一列Student数据,当Student对象同时满足ID为2、Name为Tom的时候条目就高亮显示,示例的XAML代码如下: <Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="123.435" Width="300"> <Window.Resources> <Style TargetType="ListBoxItem"> <!--使用Style设置DataTemplate--> <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> <!--MultiDataTrigger--> <Style.Triggers> <MultiDataTrigger> <MultiDataTrigger.Conditions> <Condition Binding="{Binding Path=ID}" Value="2"/> <Condition Binding="{Binding Path=Name}" Value="Tom"/> </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> </Window>
后台代码: public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); List<Student> studentList = new List<Student>() {new Student(){ ID=1, Name="Tim", Age=21}, new Student(){ ID=2, Name="Tom", Age=22 }}; this.listBoxStudent.ItemsSource = studentList; } } public class Student { public int ID { get; set; } public string Name { get; set; } public int Age { get; set; } }
由事件触发的EventTrigger EventTrigger是触发器中最特殊的一个,它是由事件来触发,被触发后执行一段动画,U1层的动画效果往往与EventTrigger相关联。 创建了一个针对Button的Style,这个Style包含两个EventTrigger,一个由MouseEnter事件触发,另一个由MouseLeave事件触发。XAML代码如下: <!--由事件触发的EventTrigger--> <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>
来源:https://blog.csdn.net/lvxingzhe3/article/details/129941884