WPF 詳解模板
在WPF中有三大模板ControlTemplate,ItemsPanelTemplate,DataTemplate.其中ControlTemplate和ItemsPanelTemplate是控件模板,DataTemplate是數(shù)據(jù)模板,他們都派生自FrameworkTemplate抽象類。
1、ControlTemplate
ControlTemplate:控件模板主要有兩個(gè)重要屬性:VisualTree內(nèi)容屬性和Triggers觸發(fā)器。所謂VisualTree(視覺樹),就是呈現(xiàn)我們所畫的控件。Triggers可以對我們的視覺樹上的元素進(jìn)行一些變化。一般用于單內(nèi)容控件。
畫一個(gè)按鈕模板來舉例說明:
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<Ellipse Width="100" Height="100">
<Ellipse.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="blue"/>
<GradientStop Offset="1" Color="LightBlue"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse Width="80" Height="80">
<Ellipse.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="White"/>
<GradientStop Offset="1" Color="Transparent"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Button Content="Hello WPF"/>
ControlTemplate之子 ContentControl和ContentPresenter
我們在ControlTemplate中畫了兩個(gè)橢圓,應(yīng)用于所有的Button按鈕,但我們Button中有Content屬性(內(nèi)容為Hello WPF),卻沒有顯示出來。因?yàn)檫@里用ControlTemplate重寫了Button的樣式,所以我們也要在ControlTemplate中增加ContentControl。通過ContentControl中的Content來綁定父容器的Content屬性。

<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<Ellipse Width="100" Height="100">
<Ellipse.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="blue"/>
<GradientStop Offset="1" Color="LightBlue"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse Width="80" Height="80">
<Ellipse.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="White"/>
<GradientStop Offset="1" Color="Transparent"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<ContentControl VerticalAlignment="Center" HorizontalAlignment="Center" Content="{TemplateBinding Content}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
我們來看一下,ContentControl繼承于Control的,用MSDN的話是:表示包含單項(xiàng)內(nèi)容的控件、ContentControl 可以包含任何類型的公共語言運(yùn)行庫對象。如下ContentControl類圖。
為了提高性能,我們可以用一個(gè)ControlPresenter來代替ContentControl,效果還是一樣,那他們有什么區(qū)別呢?
來看下ControlPresenter這個(gè)類,它繼承于FreameworkElement,如下圖:
ControlPresenter 通常叫做內(nèi)容占位符。所以我們可以看到
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>來代替<ContentControl VerticalAlignment="Center" HorizontalAlignment="Center" Content="{TemplateBinding Content}"/> 。這里少了Content綁定父容器,因?yàn)镃ontrolPresenter有個(gè)隱式的Content="{TemplateBinding Content}",也就是你可以寫也可以不寫它。
從他們的基類可以看出,ContentControl比ContentPresenter大多了。其實(shí)ControlPresenter是一個(gè)原始的構(gòu)建塊,而ContentControl是一個(gè)帶控件模板的成熟控件(里面包含ControlPresenter)。
所以我們一般用ControlPresenter。
ControlTemplate的VisualTree我們講過了,下面看下他的trigger如何運(yùn)用。
我們在原來的代碼中增加
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="ellipse1" Property="Fill" Value="Red"/>
</Trigger>
</ControlTemplate.Triggers>
當(dāng)我們把鼠標(biāo)移上去的時(shí)候就會(huì)變成如下圖所示:
發(fā)揮我們的想象力,我們可以根據(jù)ControlTemplate做更多的特效。如下
<Style TargetType="CheckBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<DockPanel>
<ContentPresenter DockPanel.Dock="Left" VerticalAlignment="Center" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" Grid.ColumnSpan="2" Fill="Gray"/>
<TextBlock x:Name="txtBox" Foreground="White" />
</Grid>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="txtBox" Property="Grid.Column" Value="1"/>
<Setter TargetName="txtBox" Property="Text" Value="On"/>
<Setter TargetName="txtBox" Property="Background" Value="LightBlue"/>
</Trigger>
<Trigger Property="IsChecked" Value="{x:Null}">
<Setter TargetName="txtBox" Property="Grid.Column" Value="0"/>
</Trigger>
<Trigger Property="IsChecked" Value="false">
<Setter TargetName="txtBox" Property="Grid.Column" Value="0"/>
<Setter TargetName="txtBox" Property="Text" Value="OFF"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Grid>
<CheckBox Width="100" Height="30" Content="Click Me"/>
</Grid>
2、ItemsPanelTemplate
ItemsPanelTemplate在MSDN的解釋是:ItemsPanelTemplate 指定用于項(xiàng)的布局的面板。 GroupStyle 具有一個(gè)類型為 ItemsPanelTemplate 的 Panel 屬性。 ItemsControl 類型具有一個(gè)類型為ItemsPanelTemplate 的 ItemsPanel 屬性。
我們先講ItemTemplate。它一般用在多個(gè)內(nèi)容控件的模板。比如ListBox。
如下看ListBox應(yīng)用ItemTemplate:
在xaml中
<Style TargetType="ListBox"> <Setter Property="ItemTemplate"> <Setter.Value> <DataTemplate> <Image Source="{Binding UriSource}" Width="100" Height="100"/> </DataTemplate> </Setter.Value> </Setter> </Style>
<ListBox x:Name="listBox" />
在后臺(tái)代碼我們給它一些圖片來填充這個(gè)ListBox.

public partial class ListBoxUserControl : UserControl
{
public ListBoxUserControl()
{
InitializeComponent();
listBox.ItemsSource = LoadImages();
}
public List<BitmapImage> LoadImages()
{
List<BitmapImage> bitmapImages=new List<BitmapImage>();
DirectoryInfo directoryInfo = new DirectoryInfo(@"E:\WPFDEMO\ControlTest\ControlTest\Images");
foreach (var item in directoryInfo.GetFiles("*.jpg"))
{
Uri uri=new Uri(item.FullName);
bitmapImages.Add(new BitmapImage(uri));
}
return bitmapImages;
}
}
我們?nèi)绻胱寛D片以橫向顯示。一開始我以為用StackPanel的Orientation=”Horiziontal”,發(fā)現(xiàn)犯了個(gè)錯(cuò)誤。這樣設(shè)置是現(xiàn)在把Items中某一個(gè)item中的內(nèi)容水平顯示啊。這時(shí)就要用到ItemsPanelTemplate這個(gè)模板了。我們在ListBox的樣式中增加如下紅色區(qū)域的代碼:
<Style TargetType="ListBox"> <Setter Property="ItemTemplate"> <Setter.Value> <DataTemplate> <StackPanel Orientation="Horizontal"> <Image Source="{Binding UriSource}" Width="100" Height="100"/> <TextBlock Text="qq" Background="Red"/> </StackPanel> </DataTemplate> </Setter.Value> </Setter>
<Setter Property="ItemsPanel"> <Setter.Value> <ItemsPanelTemplate> <StackPanel /> </ItemsPanelTemplate> </Setter.Value> </Setter>
</Style>
這里我在DataTemplate中加了StackPanel 為了說明加在這里的效果只是區(qū)分:Items整體橫向和Item中內(nèi)容的區(qū)別。
現(xiàn)在我們要想讓布局可以隨著窗體寬度變化,我們只要把ItemsPanelTemplate中的StackPanel 改成WrapPanel,并且設(shè)置ListBox的ScrollViewer.HorizontalScrollBarVisibility="Disabled",這樣才可以看到效果。
ControlTemplate
之 ItemsPresenter和ContentPresenter
我們先構(gòu)造一個(gè)TreeView
xaml中:
在開始加個(gè)Loaded="UserControl_Loaded"
<Grid>
<TreeView x:Name="treeview" />
</Grid>
后臺(tái)代碼中:

public class Node
{
private IList<Node> _childNodes;
private string _name;
public Node()
{}
public Node(string name)
{
_name = name;
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public IList<Node> ChildNodes
{
get
{
if (_childNodes==null)
_childNodes=new List<Node>();
return _childNodes;
}
}
}
public partial class TreeViewUserControl : UserControl
{
public TreeViewUserControl()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
treeview.PreviewKeyDown += (o,a) => { a.Handled = true; };
PopulateTreeView();
}
void PopulateTreeView()
{
Node rootNode=new Node("GrandFather");
for (int i = 0; i < 2; i++)
{
Node child=new Node("Father");
rootNode.ChildNodes.Add(child);
for (int j = 0; j < 3; j++)
{
Node child2=new Node("Son");
child.ChildNodes.Add(child2);
}
}
Node dummy=new Node();
dummy.ChildNodes.Add(rootNode);
treeview.ItemsSource = dummy.ChildNodes;
}
}
沒有任何樣式的TreeView
下面我們?nèi)绾芜\(yùn)用ItemsPresenter和ContentPresenter來添加樣式。來實(shí)現(xiàn)下面這幅圖的效果
在TreeView中ItemsPresenter和ContentPresenter是什么關(guān)系:ContentPresenter
是用來顯示TreeView中Item的內(nèi)容 。ItemsPresenter
是用來顯示它的子項(xiàng)(Item的子項(xiàng),也就是說child’s Items)。

<Style TargetType="TreeViewItem">
<Style.Resources>
<LinearGradientBrush x:Key="ItemAreaBrush" StartPoint="0,0.5" EndPoint="0.5,1">
<GradientStop Offset="0" Color="#66000000"/>
<GradientStop Offset="1" Color="#22000000"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="SelectedItemAreaBrush" StartPoint="0.5, 0" EndPoint="0.5, 1">
<GradientStop Color="Orange" Offset="0" />
<GradientStop Color="OrangeRed" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="ItemBorderBrush" StartPoint="0.5, 0" EndPoint="0.5, 1">
<GradientStop Color="LightGray" Offset="0" />
<GradientStop Color="Gray" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="SelectedItemBorderBrush" StartPoint="0.5, 0" EndPoint="0.5, 1">
<GradientStop Color="Yellow" Offset="0" />
<GradientStop Color="Black" Offset="1" />
</LinearGradientBrush>
<DropShadowBitmapEffect x:Key="DropShadowEffect"/>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeViewItem">
<Grid Margin="2">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border x:Name="border" Background="{StaticResource ResourceKey=ItemAreaBrush}"
BorderBrush="{StaticResource ItemBorderBrush}" BorderThickness="1" CornerRadius="8" Padding="6">
<ContentPresenter ContentSource="Header" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Border>
<ItemsPresenter Grid.Row="1"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="border" Property="Panel.Background" Value="{StaticResource SelectedItemAreaBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
看紅色代碼的部分,這里ContentPresenter顯示了父容器的Header的內(nèi)容,比如GrandFather,Father,Son.而ItemsPresenter則是否讓他顯示其子元素。比如GrandFather的子元素為Father,沒有設(shè)置<ItemsPresenter Grid.Row="1"/> 的話。子元素Father是不會(huì)顯示的。
這里為了突出層次化,運(yùn)用了ItemsPanelTemplate。最后我們在資源里引用這個(gè)樣式。

<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Controls/TreeViewItemStyle.xaml"/>
</ResourceDictionary.MergedDictionaries>
<HierarchicalDataTemplate DataType="{x:Type controls:Node}" ItemsSource="{Binding ChildNodes}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</ResourceDictionary>
</UserControl.Resources>
3、DataTemplate和HierarchicalDataTemplate
DataTemplate就是顯示綁定數(shù)據(jù)對象的模板。
HierarchicalDataTemplate繼承于DataTemplate,它專門對TreeViewItem 或 MenuItem的一些數(shù)據(jù)對象的綁定。
想了解下DataTemplate和HierarchicalDataTemplate,就看以上的例子吧。他們中只要有控件的綁定都會(huì)用到。想了解的更深一步,請看下南柯之石的這篇文章。