在前面一篇我們粗略說了Style,如果要自定義一個個性十足的控件,僅僅依靠樣式和行爲是不行的,他們只能通過控件的既有屬性來簡單改變外觀,還需要有ControlTemplate來徹底定製,這是改變Control的呈現,也可以通過DataTemplate來改變Data的呈現,對於ItemsControl,還可以通過ItemsPanelTemplate來改變Items容器的呈現。
從上段文字可以總結出,模板大概分爲3種:(1)ControlTemplate;(2)DataTemplate;(3)ItemsControl,第一種我們經常能用到,比較常見,現在我們來一一介紹:
(一)ControlTemplate
控件模板可以將自定義模板應用到某一特定類型的所有控件,主要有兩個重要屬性:VisualTree內容屬性和Triggers觸發器。所謂VisualTree(視覺樹),就是呈現我們所畫的控件。Triggers可以對我們的視覺樹上的元素進行一些變化,一般用於單內容控件。
<Window.Resources>
<ControlTemplate TargetType="{x:Type Button}" x:Key="ButtonControlTemplate">
<Grid>
<Ellipse Width="100" Height="100" >
<Ellipse.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="Cyan" />
<GradientStop Offset="1" Color="LightCyan" />
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse Width="80" Height="80">
<Ellipse.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="Yellow" />
<GradientStop Offset="1" Color="Transparent" />
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Window.Resources>
<Grid>
<Button Content="Hi,WPF" Template="{StaticResource ButtonControlTemplate}" Click="Button_Click"/>
</Grid>
上面代碼自定義了一個Button控件模板,爲什麼能替代默認模板?我們就要"解剖"Button來了解其內部結構,VS2013自帶的Expression Blend 5就具有這樣的解剖功能,讓我們來看看模板源碼:
<Style x:Key="FocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<SolidColorBrush x:Key="Button.Static.Background" Color="#FFDDDDDD"/>
<SolidColorBrush x:Key="Button.Static.Border" Color="#FF707070"/>
<SolidColorBrush x:Key="Button.MouseOver.Background" Color="#FFBEE6FD"/>
<SolidColorBrush x:Key="Button.MouseOver.Border" Color="#FF3C7FB1"/>
<SolidColorBrush x:Key="Button.Pressed.Background" Color="#FFC4E5F6"/>
<SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/>
<SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/>
<SolidColorBrush x:Key="Button.Disabled.Border" Color="#FFADB2B5"/>
<SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383"/>
<Style x:Key="ButtonStyle1" TargetType="{x:Type Button}">
<Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
<Setter Property="Background" Value="{StaticResource Button.Static.Background}"/>
<Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Padding" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true">
<ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsDefaulted" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.MouseOver.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.MouseOver.Border}"/>
</Trigger>
<Trigger Property="IsPressed" Value="true">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.Pressed.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Pressed.Border}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" TargetName="border" Value="{StaticResource Button.Disabled.Background}"/>
<Setter Property="BorderBrush" TargetName="border" Value="{StaticResource Button.Disabled.Border}"/>
<Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="{StaticResource Button.Disabled.Foreground}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
可以看出,其模板是由一個Border裏面放了一個ContentPresenter構成的,然後是觸發器定義的默認行爲,相對比較簡單,現在已經被我們新定義的替代了,因爲新模板裏沒寫Trigger,所以沒有像默認的那種觸發效果。這個Button內部比較簡單,就一個ControlTemplate,像ScrollBar等控件,內部是相當複雜的。
(二)DataTemplate
數據模板,在WPF中,決定數據外觀的是DataTemplate,即DataTemplate是數據內容的表現形式,一條數據顯示成什麼樣子,是簡單的文本還是直觀的圖形,就是由DataTemplate決定的。
內容控件通過ContentTemplate屬性支持數據模板;列表控件(即繼承自ItemsControl類的控件),通過ItemTemplate屬性支持數據模板,該模板用於顯示由ItemSource提供集合中的每一項。下面通過設計繼承ItemsControl類控件ListBox及ComboBox控件的DataTemplate,把單調的數據顯示成直觀的柱狀圖。
<Window.Resources>
<DataTemplate x:Key="MyItem">
<StackPanel Orientation="Horizontal">
<Grid>
<Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding Price}"></Rectangle>
<TextBlock Text="{Binding Year}"></TextBlock>
</Grid>
<TextBlock Text="{Binding Price}"></TextBlock>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox x:Name="listBox1" ItemTemplate="{StaticResource MyItem}"/>
<ComboBox x:Name="comboBox1" ItemTemplate="{StaticResource MyItem}"/>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Unit> units = new List<Unit>();
Unit unit1 = new Unit() { Year = "2001", Price = 100 };
Unit unit2 = new Unit() { Year = "2002", Price = 120 };
Unit unit3 = new Unit() { Year = "2003", Price = 140 };
Unit unit4 = new Unit() { Year = "2004", Price = 160 };
Unit unit5 = new Unit() { Year = "2005", Price = 180 };
units.Add(unit1);
units.Add(unit2);
units.Add(unit3);
units.Add(unit4);
units.Add(unit5);
listBox1.ItemsSource = units;
comboBox1.ItemsSource = units;
}
}
class Unit
{
public string Year { get; set; }
public int Price { get; set; }
}
也可以把某個類型作用在DataTemplate上,方法是設置DataTemplate的DataType屬性。上面的例子也可以通過這種方式實現:
//Attention
xmlns:local="clr-namespace:DataTemplate"
xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyUnit}">
<StackPanel Orientation="Horizontal">
<Grid>
<Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding Price}"></Rectangle>
<TextBlock Text="{Binding Year}"></TextBlock>
</Grid>
<TextBlock Text="{Binding Price}"></TextBlock>
</StackPanel>
</DataTemplate>
<c:ArrayList x:Key="ds">
<local:MyUnit Year = "2001" Price="100"/>
<local:MyUnit Year = "2002" Price="120"/>
<local:MyUnit Year = "2003" Price="140"/>
<local:MyUnit Year = "2004" Price="160"/>
<local:MyUnit Year = "2005" Price="180"/>
</c:ArrayList>
</Window.Resources>
<Grid>
<StackPanel>
<ListBox x:Name="listBox1" ItemsSource="{StaticResource ds}"></ListBox>
<ComboBox x:Name="comboBox1" ItemsSource="{StaticResource ds}"></ComboBox>
</StackPanel>
</Grid>
public class MyUnit
{
public string Year { get; set; }
public int Price { get; set; }
}
上面例子示範的是然繼承自ItemsControl類的控件的DataTemplate,下面就寫個ContentTemplate支持的數據模板:
<Window.Resources>
<DataTemplate x:Key="roundbutton">
<Border CornerRadius="8" Width="120" Background="Yellow" Opacity="0.8">
<TextBlock Text="{TemplateBinding Content}" HorizontalAlignment="Center"/>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<StackPanel x:Name="stackpanel">
<Button Width="200" Height="150" Margin="20" ContentTemplate="{StaticResource roundbutton}" Content="OK"/>
</StackPanel>
</Grid>
(三)ItemsPanelTemplate
ItemsPanelTemplate在MSDN的解釋是:ItemsPanelTemplate 指定用於項的佈局的面板。 GroupStyle 具有一個類型爲 ItemsPanelTemplate 的 Panel屬性。 ItemsControl 類型具有一個類型爲ItemsPanelTemplate 的 ItemsPanel 屬性。ItemsPanelTemplate用於指定項的佈局。下面,我們先看個ItemsControl類型的例子,請注意比較。
<Grid>
<ListBox HorizontalAlignment="Left" Height="70" Margin="47,0,0,0" VerticalAlignment="Top" Width="39">
<system:String>abc</system:String>
<system:String>def</system:String>
<system:String>hij</system:String>
</ListBox>
<ListBox HorizontalAlignment="Left" Height="40" Margin="47,100,0,0" VerticalAlignment="Top" Width="100">
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Style>
<system:String>abc</system:String>
<system:String>def</system:String>
<system:String>hij</system:String>
</ListBox>
</Grid>
總結
模板分爲三大類:DataTemplate 數據外衣、ControlTemplate 控件外衣及ItemsPanelTemplate 項佈局,這極大豐富了我們對各種樣式的需求。上文粗略地按我的方式分3種,當然仁者見仁,很多博客也有不同的劃分方法。考慮到篇幅,後續幾章節,我會再細細介紹模板所需用到的其它知識點。