WPF教程(十一)模板入門一

在前面一篇我們粗略說了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種,當然仁者見仁,很多博客也有不同的劃分方法。考慮到篇幅,後續幾章節,我會再細細介紹模板所需用到的其它知識點。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章