今天說下wpf中的模板,前面一篇中我們講到了style,但是style所能做的僅僅是在現有控件的基礎上進行修修補補,但是如果我們想
徹底顛覆控件樣式,那么我們就必須使用這一篇所說的模板。
老外寫書都喜歡在篇頭搞一個類圖,方便我們宏觀認識,這里我也上一個。
一:控件模板
1:ControlTemplate
我們知道wpf的控件都是繼承自Control,在Control類中有一個Template屬性,類型就是ControlTemplate。
那么利用這個ControlTemplate就可以徹底的顛覆控件的默認外觀,這里我把一個checkbox變成一個小矩形,蠻有意思的。
1 <Window x:Class="WpfApplication1.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:sys="clr-namespace:System;assembly=mscorlib" 5 Title="MainWindow" Height="350" Width="525"> 6 <Window.Resources> 7 <ControlTemplate x:Key="rect" TargetType="{x:Type CheckBox}"> 8 <StackPanel> 9 <Rectangle Name="breakRectangle" Stroke="Red" StrokeThickness="2" Width="20" Height="20"> 10 <Rectangle.Fill> 11 <SolidColorBrush Color="White"/> 12 </Rectangle.Fill> 13 </Rectangle> 14 </StackPanel> 15 </ControlTemplate> 16 </Window.Resources> 17 <Canvas> 18 <CheckBox Template="{StaticResource ResourceKey=rect}" Content="我是CheckBox"/> 19 </Canvas> 20 </Window>
確實,我們干了一件漂亮的事情,把checkbox變成了“小矩形”,但是我們發現了一個小問題,為什么我的Content=“xxx”沒有顯示到模板上?
很簡單,我們已經重定義了控件模板,默認模板將會被覆蓋...
2:ContentPresenter
幸好,wpf給我們提供了一個ContentPresenter,它的作用就是把原有模板的屬性原封不動的投放到自定義模板中。
1 <Window x:Class="WpfApplication1.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:sys="clr-namespace:System;assembly=mscorlib" 5 Title="MainWindow" Height="350" Width="525"> 6 <Window.Resources> 7 <ControlTemplate x:Key="rect" TargetType="{x:Type CheckBox}"> 8 <StackPanel> 9 <Rectangle Name="breakRectangle" Stroke="Red" StrokeThickness="2" Width="20" Height="20"> 10 <Rectangle.Fill> 11 <SolidColorBrush Color="White"/> 12 </Rectangle.Fill> 13 </Rectangle> 14 <ContentPresenter/> 15 </StackPanel> 16 </ControlTemplate> 17 </Window.Resources> 18 <Canvas> 19 <CheckBox Template="{StaticResource ResourceKey=rect}" Content="我是CheckBox"/> 20 </Canvas> 21 </Window>
當然你也可以玩一些小技巧,比如我想在"矩形“和”文字”中間設置邊距,那么我們可以設置ContentPresenter的margin。
1 <Window x:Class="WpfApplication1.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:sys="clr-namespace:System;assembly=mscorlib" 5 Title="MainWindow" Height="350" Width="525"> 6 <Window.Resources> 7 <ControlTemplate x:Key="rect" TargetType="{x:Type CheckBox}"> 8 <StackPanel> 9 <Rectangle Name="breakRectangle" Stroke="Red" StrokeThickness="2" Width="20" Height="20"> 10 <Rectangle.Fill> 11 <SolidColorBrush Color="White"/> 12 </Rectangle.Fill> 13 </Rectangle> 14 <ContentPresenter Margin="10" /> 15 </StackPanel> 16 </ControlTemplate> 17 </Window.Resources> 18 <Canvas> 19 <CheckBox Template="{StaticResource ResourceKey=rect}" Content="我是CheckBox"/> 20 </Canvas> 21 </Window>
如果你夠聰明,你會發現我設置的margin是一個非常呆板的事情,意思就是說能不能根據具體控件靈活控制margin呢?答案肯定是沒問題的,
因為我們記得一個控件可以綁定到另一個控件上,比如這里我將模板中的Margin綁定到原控件中的Padding上去。
1 <Window x:Class="WpfApplication1.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:sys="clr-namespace:System;assembly=mscorlib" 5 Title="MainWindow" Height="350" Width="525"> 6 <Window.Resources> 7 <ControlTemplate x:Key="rect" TargetType="{x:Type CheckBox}"> 8 <StackPanel> 9 <Rectangle Name="breakRectangle" Stroke="Red" StrokeThickness="2" Width="20" Height="20"> 10 <Rectangle.Fill> 11 <SolidColorBrush Color="White"/> 12 </Rectangle.Fill> 13 </Rectangle> 14 <ContentPresenter Margin="{TemplateBinding Padding}" /> 15 </StackPanel> 16 </ControlTemplate> 17 </Window.Resources> 18 <Canvas> 19 <CheckBox Template="{StaticResource ResourceKey=rect}" Content="我是CheckBox" Padding="10"/> 20 </Canvas> 21 </Window>
3:Trigger
我們知道style里面也是有trigger的,廢話不多說,上代碼說話。
1 <Window x:Class="WpfApplication1.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:sys="clr-namespace:System;assembly=mscorlib" 5 Title="MainWindow" Height="350" Width="525"> 6 <Window.Resources> 7 <ControlTemplate x:Key="rect" TargetType="{x:Type CheckBox}"> 8 <ControlTemplate.Resources> 9 <SolidColorBrush x:Key="redBrush" Color="Red"/> 10 </ControlTemplate.Resources> 11 <StackPanel> 12 <Rectangle Name="breakRectangle" Stroke="Red" StrokeThickness="2" Width="20" Height="20"> 13 <Rectangle.Fill> 14 <SolidColorBrush Color="White"/> 15 </Rectangle.Fill> 16 </Rectangle> 17 <ContentPresenter/> 18 </StackPanel> 19 <ControlTemplate.Triggers> 20 <Trigger Property="IsChecked" Value="True"> 21 <Setter TargetName="breakRectangle" Property="Fill" Value="{StaticResource ResourceKey=redBrush}"> 22 </Setter> 23 </Trigger> 24 </ControlTemplate.Triggers> 25 </ControlTemplate> 26 </Window.Resources> 27 <Canvas> 28 <CheckBox Template="{StaticResource ResourceKey=rect}" Content="我是CheckBox"/> 29 </Canvas> 30 </Window>
最后形成的效果就是當checkbox選中時為實心框,不選中為空心框。
4:與Style混搭
可能剛才我也說了,style只能在原有的控件基礎上修修補補,如果我們讓Style修補Control控件的Template屬性時,此時我們是不是
就可以實現ControlTemplate和Style的混搭呢?
1 <Window x:Class="WpfApplication1.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:sys="clr-namespace:System;assembly=mscorlib" 5 Title="MainWindow" Height="350" Width="525"> 6 <Window.Resources> 7 <Style x:Key="cbx" TargetType="{x:Type CheckBox}"> 8 <Setter Property="Template"> 9 <Setter.Value> 10 <ControlTemplate TargetType="{x:Type CheckBox}"> 11 <ControlTemplate.Resources> 12 <SolidColorBrush x:Key="redBrush" Color="Red"/> 13 </ControlTemplate.Resources> 14 <StackPanel> 15 <Rectangle Name="breakRectangle" Stroke="Red" StrokeThickness="2" Width="20" Height="20"> 16 <Rectangle.Fill> 17 <SolidColorBrush Color="White"/> 18 </Rectangle.Fill> 19 </Rectangle> 20 <ContentPresenter/> 21 </StackPanel> 22 <ControlTemplate.Triggers> 23 <Trigger Property="IsChecked" Value="True"> 24 <Setter TargetName="breakRectangle" Property="Fill" Value="{StaticResource ResourceKey=redBrush}"> 25 </Setter> 26 </Trigger> 27 </ControlTemplate.Triggers> 28 </ControlTemplate> 29 </Setter.Value> 30 </Setter> 31 </Style> 32 33 </Window.Resources> 34 <Canvas> 35 <CheckBox Style="{StaticResource ResourceKey=cbx}" Content="我是CheckBox"/> 36 </Canvas> 37 </Window>
二:數據模板
現在我們已經知道“控件模板”是用於改變控件外觀,那么“數據模板”顧名思義就是控制數據的顯示方式,下面做個demo讓person綁定到listbox上。
1 namespace WpfApplication1 2 { 3 /// <summary> 4 /// MainWindow.xaml 的交互邏輯 5 /// </summary> 6 public partial class MainWindow : Window 7 { 8 public static string name = "一線碼農"; 9 10 public MainWindow() 11 { 12 InitializeComponent(); 13 } 14 } 15 16 public class PersonList : ObservableCollection<Person> 17 { 18 public PersonList() 19 { 20 this.Add(new Person() { Name = "一線碼農", Age = 24, Address = "上海" }); 21 this.Add(new Person() { Name = "小師妹", Age = 20, Address = "上海" }); 22 } 23 } 24 25 public class Person 26 { 27 public string Name { get; set; } 28 29 public int Age { get; set; } 30 31 public string Address { get; set; } 32 } 33 }
xaml:
1 <Window x:Class="WpfApplication1.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:sys="clr-namespace:System;assembly=mscorlib" 5 xmlns:src="clr-namespace:WpfApplication1" 6 Title="MainWindow" Height="350" Width="525"> 7 <Window.Resources> 8 <ObjectDataProvider x:Key="personList" ObjectType="{x:Type src:PersonList}"/> 9 </Window.Resources> 10 <Grid> 11 <ListBox ItemsSource="{Binding Source={StaticResource ResourceKey=personList}}"></ListBox> 12 </Grid> 13 </Window>
最后我們發現,listbox中並沒有呈現我們需要的數據,只是呈現了當前類的ToString()方法,很簡單,因為我們綁定的不是簡單的數據類型集合,
而是多字段的復雜類型,更重要的是我們並沒有告訴wpf該如何呈現person數據。
<1>重寫Tostring()
既然wpf在Render數據的時候呈現的是當前的ToString()形式,那下面我們來重寫ToString()試試看。
1 public class Person 2 { 3 public string Name { get; set; } 4 5 public int Age { get; set; } 6 7 public string Address { get; set; } 8 9 public override string ToString() 10 { 11 return string.Format("姓名:{0}, 年齡:{1}, 地址:{2}", Name, Age, Address); 12 } 13 }
最后看看效果,如我們所願,person信息已經呈現。
<2>DataTemplate重寫
或許有的人比較苛刻,他需要person是作為矩形一塊一塊的呈現,而不是這些簡單的形式,那么此時我們就可以用DataTemplate來顛覆。
1 <Window x:Class="WpfApplication1.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:sys="clr-namespace:System;assembly=mscorlib" 5 xmlns:src="clr-namespace:WpfApplication1" 6 Title="MainWindow" Height="350" Width="525"> 7 <Window.Resources> 8 <ObjectDataProvider x:Key="personList" ObjectType="{x:Type src:PersonList}"/> 9 <DataTemplate x:Key="rect"> 10 <Border Name="border" BorderBrush="Aqua" BorderThickness="1" Padding="5" Margin="5"> 11 <StackPanel> 12 <StackPanel Orientation="Horizontal"> 13 <TextBlock Text="{Binding Name}" Margin="5,0,0,0"/> 14 <TextBlock Text="{Binding Age}" Margin="5,0,0,0"/> 15 </StackPanel> 16 <StackPanel Orientation="Horizontal"> 17 <TextBlock Text="{Binding Address}" Margin="5,0,0,0"/> 18 </StackPanel> 19 </StackPanel> 20 </Border> 21 </DataTemplate> 22 </Window.Resources> 23 <Grid> 24 <ListBox ItemsSource="{Binding Source={StaticResource ResourceKey=personList}}" 25 ItemTemplate="{StaticResource ResourceKey=rect}"></ListBox> 26 </Grid> 27 </Window>
哈哈,果然是以一塊一塊的形式展現,大功告成,當然這里的”觸發器“和”style混搭“跟ConrolTemplate非常相似,我想應該不需要累贅了。
三: ItemsPanelTemplate
在條目控件(ItemControl)里面,有一個屬性叫ItemPanel,類型是ItemPanelTemplate。
那么ItemsPanelTemplate主要用來干什么的呢?首先我們要知道常見的條目控件有:ListBox,Menu,StatusBar,比如拿ListBox來說,
我們經過仔細研究,發現ItemBox的ItemPanel其實是一個VisualizingStackPanel,就是說ListBox的每一項的排列方式是遵循StackPanel的
原則,也就是從上到下的排列方式,比如”一線碼農“和”小師妹“是按照豎行排列方式,好,我現在的要求就是能夠”橫排“,該如何做到呢?
1 <Window x:Class="WpfApplication1.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:sys="clr-namespace:System;assembly=mscorlib" 5 xmlns:src="clr-namespace:WpfApplication1" 6 Title="MainWindow" Height="350" Width="525"> 7 <Window.Resources> 8 <ObjectDataProvider x:Key="personList" ObjectType="{x:Type src:PersonList}"/> 9 <DataTemplate x:Key="rect"> 10 <Border Name="border" BorderBrush="Aqua" BorderThickness="1" Padding="5" Margin="5"> 11 <StackPanel> 12 <StackPanel Orientation="Horizontal"> 13 <TextBlock Text="{Binding Name}" Margin="5,0,0,0"/> 14 <TextBlock Text="{Binding Age}" Margin="5,0,0,0"/> 15 </StackPanel> 16 <StackPanel Orientation="Horizontal"> 17 <TextBlock Text="{Binding Address}" Margin="5,0,0,0"/> 18 </StackPanel> 19 </StackPanel> 20 </Border> 21 </DataTemplate> 22 <ItemsPanelTemplate x:Key="items"> 23 <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center"/> 24 </ItemsPanelTemplate> 25 </Window.Resources> 26 <Grid> 27 <ListBox ItemsSource="{Binding Source={StaticResource ResourceKey=personList}}" 28 ItemTemplate="{StaticResource ResourceKey=rect}" ItemsPanel="{StaticResource ResourceKey=items}"></ListBox> 29 </Grid> 30 </Window>
哈哈,確實有意思,我們改變了ListBox中Item的默認排序方向,當然在menu,statusBar中我們也可以用同樣的方式來更改。
四: HierarchicalDataTemplate
它是針對具有分層數據結構的控件設計的,比如說TreeView,相當於可以每一個層級上做DataTemplate,很好很強大。
1 <Window x:Class="WpfApplication1.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:sys="clr-namespace:System;assembly=mscorlib" 5 xmlns:src="clr-namespace:WpfApplication1" 6 Title="MainWindow" Height="350" Width="525"> 7 <Window.Resources> 8 <XmlDataProvider x:Key="Info" XPath="Nations"> 9 <x:XData> 10 <Nations xmlns=""> 11 <Nation Name="中國"> 12 <Provinces> 13 <Province Name="安徽"> 14 <Citys> 15 <City Name="安慶"> 16 <Countrys> 17 <Country Name="潛山"/> 18 <Country Name="桐城"/> 19 </Countrys> 20 </City> 21 <City Name="合肥"> 22 <Countrys> 23 <Country Name="長豐"/> 24 <Country Name="肥東"/> 25 </Countrys> 26 </City> 27 </Citys> 28 </Province> 29 <Province Name="江蘇"> 30 <Citys> 31 <City Name="南京"> 32 <Countys> 33 <Country Name="溧水"/> 34 <Country Name="高淳"/> 35 </Countys> 36 </City> 37 <City Name="蘇州"> 38 <Countys> 39 <Country Name="常熟"/> 40 </Countys> 41 </City> 42 </Citys> 43 </Province> 44 </Provinces> 45 </Nation> 46 </Nations> 47 </x:XData> 48 </XmlDataProvider> 49 <HierarchicalDataTemplate DataType="Nation" ItemsSource="{Binding XPath=Provinces/Province}"> 50 <StackPanel Background="AliceBlue"> 51 <TextBlock FontSize="20" Text="{Binding XPath=@Name}"/> 52 </StackPanel> 53 </HierarchicalDataTemplate> 54 <HierarchicalDataTemplate DataType="Province" ItemsSource="{Binding XPath=Citys/City}"> 55 <StackPanel Background="LightBlue"> 56 <TextBlock FontSize="18" Text="{Binding XPath=@Name}"/> 57 </StackPanel> 58 </HierarchicalDataTemplate> 59 <HierarchicalDataTemplate DataType="City" ItemsSource="{Binding XPath=Countrys/Country}"> 60 <StackPanel Background="LightBlue"> 61 <TextBlock FontSize="18" Text="{Binding XPath=@Name}"/> 62 </StackPanel> 63 </HierarchicalDataTemplate> 64 <HierarchicalDataTemplate DataType="Country"> 65 <StackPanel Background="LightSalmon"> 66 <TextBlock FontSize="18" Text="{Binding XPath=@Name}"/> 67 </StackPanel> 68 </HierarchicalDataTemplate> 69 </Window.Resources> 70 <TreeView ItemsSource="{Binding Source={StaticResource ResourceKey=Info},XPath=Nation}"></TreeView> 71 </Window>