今天说下wpf中的模板,前面一篇中我们讲到了style,但是style所能做的仅仅是在现有控件的基础上进行修修补补,但是如果我们想
彻底颠覆控件样式,那么我们就必须使用这一篇所说的模板。
老外写书都喜欢在篇头搞一个类图,方便我们宏观认识,这里我也上一个。
一:控件模板
1:ControlTemplate
我们知道wpf的控件都是继承自Control,在Control类中有一个Template属性,类型就是ControlTemplate。
那么利用这个ControlTemplate就可以彻底的颠覆控件的默认外观,这里我把一个checkbox变成一个小矩形,蛮有意思的。
<Window x:Class="WpfApplication1.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="350" Width="525">
<Window.Resources>
<ControlTemplate x:Key="rect" TargetType="{x:Type CheckBox}">
<StackPanel>
<Rectangle Name="breakRectangle" Stroke="Red" StrokeThickness="2" Width="20" Height="20">
<Rectangle.Fill>
<SolidColorBrush Color="White"/>
</Rectangle.Fill>
</Rectangle>
</StackPanel>
</ControlTemplate>
</Window.Resources>
<Canvas>
<CheckBox Template="{StaticResource ResourceKey=rect}" Content="我是CheckBox"/>
</Canvas>
</Window>
确实,我们干了一件漂亮的事情,把checkbox变成了“小矩形”,但是我们发现了一个小问题,为什么我的Content=“xxx”没有显示到模板上?
很简单,我们已经重定义了控件模板,默认模板将会被覆盖...
2:ContentPresenter
幸好,wpf给我们提供了一个ContentPresenter,它的作用就是把原有模板的属性原封不动的投放到自定义模板中。
<Window x:Class="WpfApplication1.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="350" Width="525">
<Window.Resources>
<ControlTemplate x:Key="rect" TargetType="{x:Type CheckBox}">
<StackPanel>
<Rectangle Name="breakRectangle" Stroke="Red" StrokeThickness="2" Width="20" Height="20">
<Rectangle.Fill>
<SolidColorBrush Color="White"/>
</Rectangle.Fill>
</Rectangle>
<ContentPresenter/>
</StackPanel>
</ControlTemplate>
</Window.Resources>
<Canvas>
<CheckBox Template="{StaticResource ResourceKey=rect}" Content="我是CheckBox"/>
</Canvas>
</Window>
当然你也可以玩一些小技巧,比如我想在"矩形“和”文字”中间设置边距,那么我们可以设置ContentPresenter的margin。
<Window x:Class="WpfApplication1.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="350" Width="525">
<Window.Resources>
<ControlTemplate x:Key="rect" TargetType="{x:Type CheckBox}">
<StackPanel>
<Rectangle Name="breakRectangle" Stroke="Red" StrokeThickness="2" Width="20" Height="20">
<Rectangle.Fill>
<SolidColorBrush Color="White"/>
</Rectangle.Fill>
</Rectangle>
<ContentPresenter Margin="10" />
</StackPanel>
</ControlTemplate>
</Window.Resources>
<Canvas>
<CheckBox Template="{StaticResource ResourceKey=rect}" Content="我是CheckBox"/>
</Canvas>
</Window>
如果你够聪明,你会发现我设置的margin是一个非常呆板的事情,意思就是说能不能根据具体控件灵活控制margin呢?答案肯定是没问题的,
因为我们记得一个控件可以绑定到另一个控件上,比如这里我将模板中的Margin绑定到原控件中的Padding上去。
<Window x:Class="WpfApplication1.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="350" Width="525">
<Window.Resources>
<ControlTemplate x:Key="rect" TargetType="{x:Type CheckBox}">
<StackPanel>
<Rectangle Name="breakRectangle" Stroke="Red" StrokeThickness="2" Width="20" Height="20">
<Rectangle.Fill>
<SolidColorBrush Color="White"/>
</Rectangle.Fill>
</Rectangle>
<ContentPresenter Margin="{TemplateBinding Padding}" />
</StackPanel>
</ControlTemplate>
</Window.Resources>
<Canvas>
<CheckBox Template="{StaticResource ResourceKey=rect}" Content="我是CheckBox" Padding="10"/>
</Canvas>
</Window>
3:Trigger
我们知道style里面也是有trigger的,废话不多说,上代码说话。
<Window x:Class="WpfApplication1.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="350" Width="525">
<Window.Resources>
<ControlTemplate x:Key="rect" TargetType="{x:Type CheckBox}">
<ControlTemplate.Resources>
<SolidColorBrush x:Key="redBrush" Color="Red"/>
</ControlTemplate.Resources>
<StackPanel>
<Rectangle Name="breakRectangle" Stroke="Red" StrokeThickness="2" Width="20" Height="20">
<Rectangle.Fill>
<SolidColorBrush Color="White"/>
</Rectangle.Fill>
</Rectangle>
<ContentPresenter/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="breakRectangle" Property="Fill" Value="{StaticResource ResourceKey=redBrush}">
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<Canvas>
<CheckBox Template="{StaticResource ResourceKey=rect}" Content="我是CheckBox"/>
</Canvas>
</Window>
最后形成的效果就是当checkbox选中时为实心框,不选中为空心框。
4:与Style混搭
可能刚才我也说了,style只能在原有的控件基础上修修补补,如果我们让Style修补Control控件的Template属性时,此时我们是不是
就可以实现ControlTemplate和Style的混搭呢?
<Window x:Class="WpfApplication1.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="350" Width="525">
<Window.Resources>
<Style x:Key="cbx" TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<ControlTemplate.Resources>
<SolidColorBrush x:Key="redBrush" Color="Red"/>
</ControlTemplate.Resources>
<StackPanel>
<Rectangle Name="breakRectangle" Stroke="Red" StrokeThickness="2" Width="20" Height="20">
<Rectangle.Fill>
<SolidColorBrush Color="White"/>
</Rectangle.Fill>
</Rectangle>
<ContentPresenter/>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="breakRectangle" Property="Fill" Value="{StaticResource ResourceKey=redBrush}">
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Canvas>
<CheckBox Style="{StaticResource ResourceKey=cbx}" Content="我是CheckBox"/>
</Canvas>
</Window>
二:数据模板
现在我们已经知道“控件模板”是用于改变控件外观,那么“数据模板”顾名思义就是控制数据的显示方式,下面做个demo让person绑定到listbox上。
namespace WpfApplication1
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public static string name = "一线码农";
public MainWindow()
{
InitializeComponent();
}
}
public class PersonList : ObservableCollection<Person>
{
public PersonList()
{
this.Add(new Person() { Name = "一线码农", Age = 24, Address = "上海" });
this.Add(new Person() { Name = "小师妹", Age = 20, Address = "上海" });
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
}
}
xaml:
<Window x:Class="WpfApplication1.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"
xmlns:src="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ObjectDataProvider x:Key="personList" ObjectType="{x:Type src:PersonList}"/>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource ResourceKey=personList}}"></ListBox>
</Grid>
</Window>
最后我们发现,listbox中并没有呈现我们需要的数据,只是呈现了当前类的ToString()方法,很简单,因为我们绑定的不是简单的数据类型集合,
而是多字段的复杂类型,更重要的是我们并没有告诉wpf该如何呈现person数据。
<1>重写Tostring()
既然wpf在Render数据的时候呈现的是当前的ToString()形式,那下面我们来重写ToString()试试看。
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public override string ToString()
{
return string.Format("姓名:{0}, 年龄:{1}, 地址:{2}", Name, Age, Address);
}
}
最后看看效果,如我们所愿,person信息已经呈现。
<2>DataTemplate重写
或许有的人比较苛刻,他需要person是作为矩形一块一块的呈现,而不是这些简单的形式,那么此时我们就可以用DataTemplate来颠覆。
<Window x:Class="WpfApplication1.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"
xmlns:src="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ObjectDataProvider x:Key="personList" ObjectType="{x:Type src:PersonList}"/>
<DataTemplate x:Key="rect">
<Border Name="border" BorderBrush="Aqua" BorderThickness="1" Padding="5" Margin="5">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="5,0,0,0"/>
<TextBlock Text="{Binding Age}" Margin="5,0,0,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Address}" Margin="5,0,0,0"/>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource ResourceKey=personList}}"
ItemTemplate="{StaticResource ResourceKey=rect}"></ListBox>
</Grid>
</Window>
哈哈,果然是以一块一块的形式展现,大功告成,当然这里的”触发器“和”style混搭“跟ConrolTemplate非常相似,我想应该不需要累赘了。
三: ItemsPanelTemplate
在条目控件(ItemControl)里面,有一个属性叫ItemPanel,类型是ItemPanelTemplate。
那么ItemsPanelTemplate主要用来干什么的呢?首先我们要知道常见的条目控件有:ListBox,Menu,StatusBar,比如拿ListBox来说,
我们经过仔细研究,发现ItemBox的ItemPanel其实是一个VisualizingStackPanel,就是说ListBox的每一项的排列方式是遵循StackPanel的
原则,也就是从上到下的排列方式,比如”一线码农“和”小师妹“是按照竖行排列方式,好,我现在的要求就是能够”横排“,该如何做到呢?
<Window x:Class="WpfApplication1.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"
xmlns:src="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ObjectDataProvider x:Key="personList" ObjectType="{x:Type src:PersonList}"/>
<DataTemplate x:Key="rect">
<Border Name="border" BorderBrush="Aqua" BorderThickness="1" Padding="5" Margin="5">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="5,0,0,0"/>
<TextBlock Text="{Binding Age}" Margin="5,0,0,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Address}" Margin="5,0,0,0"/>
</StackPanel>
</StackPanel>
</Border>
</DataTemplate>
<ItemsPanelTemplate x:Key="items">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</ItemsPanelTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource ResourceKey=personList}}"
ItemTemplate="{StaticResource ResourceKey=rect}" ItemsPanel="{StaticResource ResourceKey=items}"></ListBox>
</Grid>
</Window>
哈哈,确实有意思,我们改变了ListBox中Item的默认排序方向,当然在menu,statusBar中我们也可以用同样的方式来更改。
四: HierarchicalDataTemplate
它是针对具有分层数据结构的控件设计的,比如说TreeView,相当于可以每一个层级上做DataTemplate,很好很强大。
<Window x:Class="WpfApplication1.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"
xmlns:src="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<XmlDataProvider x:Key="Info" XPath="Nations">
<x:XData>
<Nations xmlns="">
<Nation Name="中国">
<Provinces>
<Province Name="安徽">
<Citys>
<City Name="安庆">
<Countrys>
<Country Name="潜山"/>
<Country Name="桐城"/>
</Countrys>
</City>
<City Name="合肥">
<Countrys>
<Country Name="长丰"/>
<Country Name="肥东"/>
</Countrys>
</City>
</Citys>
</Province>
<Province Name="江苏">
<Citys>
<City Name="南京">
<Countys>
<Country Name="溧水"/>
<Country Name="高淳"/>
</Countys>
</City>
<City Name="苏州">
<Countys>
<Country Name="常熟"/>
</Countys>
</City>
</Citys>
</Province>
</Provinces>
</Nation>
</Nations>
</x:XData>
</XmlDataProvider>
<HierarchicalDataTemplate DataType="Nation" ItemsSource="{Binding XPath=Provinces/Province}">
<StackPanel Background="AliceBlue">
<TextBlock FontSize="20" Text="{Binding XPath=@Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="Province" ItemsSource="{Binding XPath=Citys/City}">
<StackPanel Background="LightBlue">
<TextBlock FontSize="18" Text="{Binding XPath=@Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="City" ItemsSource="{Binding XPath=Countrys/Country}">
<StackPanel Background="LightBlue">
<TextBlock FontSize="18" Text="{Binding XPath=@Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="Country">
<StackPanel Background="LightSalmon">
<TextBlock FontSize="18" Text="{Binding XPath=@Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</Window.Resources>
<TreeView ItemsSource="{Binding Source={StaticResource ResourceKey=Info},XPath=Nation}"></TreeView>
</Window>