Template和Style


簡介

這是一篇記錄筆者閱讀學習劉鐵猛老師的《深入淺出WPF》的讀書筆記,如果文中內容閱讀不暢,推薦購買正版書籍詳細閱讀。

Template 模板的內涵

WPF系統不但支持傳統的Windows Forms編程的用戶界面和用戶體驗設計,更支持使用專門設計工具Microsoft Expreession Blend進行專業設計,更推出了以模板為核心的新一代設計理念

程序的本質是算法和數據結構,WPF中作為一種“形式”,它要表現的“內容”就是算法和數據結構,Binding傳遞的是數據,事件參數攜帶的也是數據;方法和委托調用的是算法,事件傳遞消息也是算法······,作為“表現形式”,每個控件都是為了實現某種用戶操作算法和直觀顯示某種數據而生,一個控件看上去是什么樣子由它的“算法內容”和“數據內容”決定,這就是內容決定形式

  • 控件的“算法內容”:指控件能展示哪些數據、具有哪些方法、能響應那些操作、能激發什么事件、簡而言之就是控件的功能,它們是一組相關的算法邏輯。
  • 控件的“數據內容”:控件所展示的具體數據是什么。

以往的GUI開發技術(Windows Forms)耦合度過高,控件內部的邏輯和數據是固定的,程序員無法改變,外觀可以操作的空間也較少,造成這個局面的根本原因就是數據和算法的”形式“和”內容“耦合度太緊了。

在WPF中,通過引入Template(模板)將數據和算法的”內容“與“形式”解耦了。WPF中的Template分為兩大類:

  • ControlTemplate 是算法內容的表現形式,一個控件怎樣組織其內部結構才能讓它更符合業務邏輯,讓用戶操作起來更舒服就是由它來控制的。它決定了控件”長什么樣子“,並讓程序員有機會在控件原有的內部邏輯基礎上擴展自己的邏輯。
  • DataTemplate 是數據內容的表現形式,一條數據顯示成什么樣子,是簡單的文本還是直觀的圖形動畫就是由它來決定。

一言蔽之,Template就是”外衣“——ControlTemplate是控件的外衣,DataTemplate是數據的外衣。

DataTemplate 數據外衣-數據內容的表現形式

DataTemplate常用的地方有3處,分別是:

  • ContentControl 的ContentTemplate 屬性,相當於給ContentControl的內容穿衣服。
  • ItemsControl 的ItemTemplate屬性,相當於給ItemsControl 的數據條目傳衣服。
  • GridViewColumn的CellTemplate屬性,相當於給GridViewColumn單元格里的數據穿衣服。

示例:

需求:有一列汽車數據,這里數據顯示在一個ListBox里,要求ListBox的條目顯示汽車的廠商標志和簡要參數,單擊某個條目后在窗口的詳細內容區域顯示汽車的照片和詳細參數。

  1. 添加資源文件夾引入對應汽車圖標
  2. 創建詳細內容窗口的DataTemplate
  3. 創建ListBox的DataTemplate
  4. 使用對應模板
  5. Binding對應數據
<Window x:Class="Template.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Template"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <!--Converters-->
        <!--創建詳細視圖的數據模板-->
        <DataTemplate x:Key="carDetailViewTemplate">
            <Border BorderBrush="Black" BorderThickness="1" CornerRadius="6">
                <StackPanel Margin="5">
                    <Image Width="400" Height="250"
                           Source="G:\VsProject\WPF練習\Template\Resources\Aodi.jpg"/>
                    <StackPanel Orientation="Horizontal" Margin="5">
                        <TextBlock Text="Name:" FontWeight="Bold" FontSize="20"/>
                        <TextBlock Text="{Binding Name}" FontSize="20" Margin="5,0"/>
                    </StackPanel>
                    <StackPanel Orientation="Horizontal" Margin="5,0">
                        <TextBlock Text="Automaker:" FontWeight="Bold"/>
                        <TextBlock Text="{Binding Automaker}" Margin="5,0"/>
                        <TextBlock Text="Year:" FontWeight="Bold"/>
                        <TextBlock Text="{Binding Year}" Margin="5,0"/>
                        <TextBlock Text="Top Speed:" FontWeight="Bold"/>
                        <TextBlock Text="{Binding TopSpeed}" Margin="5,0"/>
                    </StackPanel>
                </StackPanel>
            </Border>
        </DataTemplate>
        <!--創建ListBox條目的數據模板-->
        <DataTemplate x:Key="carListItemViewTemplate">
            <Grid Margin="2">
                <StackPanel Orientation="Horizontal">
                    <Image Grid.RowSpan="3" Width="64" Height="64"
                           Source="G:\VsProject\WPF練習\Template\Resources\Aodi.png"/>
                    <StackPanel Margin="5,0">
                        <TextBlock Text="{Binding Name}" FontSize="16" FontWeight="Bold"/>
                        <TextBlock Text="{Binding Year}" FontSize="14"/>
                    </StackPanel>
                </StackPanel>
            </Grid>
        </DataTemplate>
    </Window.Resources>
    <!--窗體內容 使用對應模板-->
    <Grid>
        <UserControl ContentTemplate="{StaticResource carDetailViewTemplate}"
                     Content="{Binding SelectedItem,ElementName=listBoxCars}"/>
        <ListBox x:Name="listBoxCars" Width="180" Margin="607,0,13,0"
                 ItemTemplate="{StaticResource carListItemViewTemplate}"/>
    </Grid>
</Window>

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        InitialCarList();
    }

    private void InitialCarList()
    {
        List<Car> carList = new List<Car>()
        {
            new Car(){Automaker = "Lamborghini",Name = "Diablo", Year = "1990",TopSpeed = "340"},
            new Car(){Automaker = "Lamborghini",Name = "Murcielago",Year = "2001",TopSpeed="353"},
            new Car(){Automaker = "Lamborghini",Name="Callardo",Year="2003",TopSpeed="325"},
            new Car(){Automaker="Lamborghini",Name="Reventon",Year="2008",TopSpeed="356"},
        };
        this.listBoxCars.ItemsSource = carList;
    }
}

ControlTemplate 控件的外衣-算法內容的表現形式

ControlTemplate的兩個作用:

  • 通過更換ControlTemplate改變控件外觀,使之具有更優的用戶使用體驗及外觀。
  • 借助ControlTemplate,程序員與設計師可以並行工作,程序員可以先用WPF標准控件進行編程,等設計師的工作完成后,只需把新的ControlTemplate應用到程序中就可以了。

示例:

  1. 文檔大綱-》選中需要設計的控件-》右鍵編輯模板-編輯副本-》設置名稱和位置
  2. 修改設計需要的模板ControTemplate,TemplateBinding將控件模板中的屬性值關聯到目標控件上,產生的效果就是你為目標控件設置的值以后,控件模板的值也會隨之改變。
  3. 目標控件使用對應的模板,Style="{DynamicResource RoundCornerTexBoxStyle}

TemplateBinding是為了某個特定場景優化出來的數據綁定版本--需要把ControlTemplate里面的某個Property綁定到應用該ControlTemplate的控件的對應Property上。
中文表達比較拗口,MSDN的原文“Links the value of a property in a control template to be the value of a property on the templated control.”

翻譯:將控件模板中屬性的值鏈接為模板化控件上的屬性的值

ItemsControl的PanelTemplate

ItemsControl具有一個名為ItemsPanel的屬性,它的數據類型為ItemsPanelTemplate,也是一種控件Template,可以控制ItemsControl的條目容器。

示例:制作一個橫向排列的ListBox

<ListBox>
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <TextBlock Text="菜單"/>
    <TextBlock Text="幫助"/>
    <TextBlock Text="請求"/>
    <TextBlock Text="張三"/>
</ListBox>

DataTemplate與ControlTemplate的關系與應用

DataTemplate與ControlTemplate的關系

控件只是數據和行為的載體、是個抽象的概念,至於它本身長什么樣子(控件的內部結構)、它的數據會長成什么樣子(數據顯示結構)都是靠Template生成的。

  • ControlTemplate決定控件的外觀,生成的控件樹的樹根是ControlTemplate的目標控件,此模塊化控件的Template屬性值就是這個ControlTemplate實例。
  • DataTemplate決定數據外觀,生成的控件樹的樹根是一個ContentPresenter控件,此模塊化控件的ContentTemplate屬性值就是這個DataTemplate示例。

因為ContentPresenter控件是ControlTemplate控件樹上的一個節點,所以DataTemplate控件樹是ControlTemplate控件樹的一棵子樹。

DataTemplate與ControlTemplate的應用

為Template設置其應用目標有兩種方法:

  1. 逐個設置控件的Template、ContentTemplate、ItemsTemplate、CellTemplate等屬性,不想應用Template的控件不設置。
  2. 把Template應用在某個類型的控件或數據上。

使用方法

  1. 把ControlTemplate應用在所有目標上需要借助Style來實現,但Style不能標記x:Key。Style沒有x:Key標記,默認為應用到所有由x:Type指定的控件上,如果不想應用則需把控件的Style標記為{x:Null}.

  2. 把DataTemplate應用在某個數據類型上的方法是設置DataTemplate的DataType屬性,並且DataTemplate作為資源時也不能帶有x:Key標記。DataTemplate具有直接把XML數據節點當作目標對象的功能——XML數據中的元素名(標簽名)可以作為DataType,元素的子節點可以使用XPath來訪問

    • HierarchicalDataTemplate層級數據模板能夠幫助層級控件顯示數據,例如:TreeView,MenuItem控件。

DataTemplate示例:

<Window.Resources>
        <DataTemplate DataType="{x:Type local:Unit}">
            <Grid>
                <StackPanel Orientation="Horizontal">
                    <Grid>
                        <Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding Price}"/>
                        <TextBlock Text="{Binding Year}"/>
                    </Grid>
                    <TextBlock Text="{Binding Price}" Margin="5"/>
                </StackPanel>
            </Grid>
        </DataTemplate>
        <!--數據源-->
        <c:ArrayList x:Key="ds">
            <local:Unit Year="2001年" Price="100"/>
            <local:Unit Year="2002年" Price="120"/>
            <local:Unit Year="2001年" Price="100"/>
            <local:Unit Year="2002年" Price="120"/>
            <local:Unit Year="2001年" Price="100"/>
        </c:ArrayList>
    </Window.Resources>
<Grid>
        <StackPanel>
            <ListBox ItemsSource="{StaticResource ds}"/>
            <ComboBox ItemsSource="{StaticResource ds}"/>
        </StackPanel>
    </Grid>
//C#代碼 
 public class Unit
 {
     public int Price { get; set; }
     public string Year { get; set; }
 }
<!--使用XPath訪問元素的子節點-->
<Window.Resources>
        <DataTemplate DataType="Unit">
            <Grid>
                <StackPanel Orientation="Horizontal">
                    <Grid>
                        <Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding XPath=@Price}"/>
                        <TextBlock Text="{Binding XPath=@Year}"/>
                    </Grid>
                    <TextBlock Text="{Binding XPath=@Price}" Margin="5"/>
                </StackPanel>
            </Grid>
        </DataTemplate>
        <!--數據源-->
        <XmlDataProvider x:Key="ds" XPath="Units/Unit">
            <x:XData>
                <Units xmlns="">
                    <Unit Year="2001年" Price="100"/>
                    <Unit Year="2002年" Price="120"/>
                    <Unit Year="2001年" Price="100"/>
                    <Unit Year="2002年" Price="120"/>
                    <Unit Year="2001年" Price="100"/>
                </Units>
            </x:XData>
        </XmlDataProvider>
</Window.Resources>
<Grid>
        <StackPanel>
            <ListBox ItemsSource="{Binding Source={StaticResource ds}}"/>
            <ComboBox ItemsSource="{Binding Source={StaticResource ds}}"/>
        </StackPanel>
</Grid>

HierarchicalDataTemplate示例:

<!--TreeView示例-->
<Window.Resources>
        <!--數據源-->
        <XmlDataProvider x:Key="ds" Source="G:\VsProject\WPF練習\TreeView\Data.xml" XPath="Data/Grade"/>
        <!--年級模板-->
        <HierarchicalDataTemplate DataType="Grade" ItemsSource="{Binding XPath=Class}">
            <TextBlock Text="{Binding XPath=@Name}"/>
        </HierarchicalDataTemplate>
        <!--班級模板-->
        <HierarchicalDataTemplate DataType="Class" ItemsSource="{Binding XPath=Group}">
            <RadioButton Content="{Binding XPath=@Name}" GroupName="gn"/>
        </HierarchicalDataTemplate>
        <!--小組模板-->
        <HierarchicalDataTemplate DataType="Group" ItemsSource="{Binding XPath=Student}">
            <CheckBox Content="{Binding XPath=@Name}"/>
        </HierarchicalDataTemplate>
</Window.Resources>
<Grid>
        <TreeView Margin="5" ItemsSource="{Binding Source={StaticResource ds}}"/>
</Grid>
<!--TreeView示例-->
<Window.Resources>
        <!--數據源-->
        <XmlDataProvider x:Key="ds" Source="G:\VsProject\WPF練習\Menu\Data.xml" XPath="Data/Operation"/>
        <!--Operation模板-->
        <HierarchicalDataTemplate DataType="Operation"
                                  ItemsSource="{Binding XPath=Operation}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding XPath=@Name}" Margin="10,0"/>
                <TextBlock Text="{Binding XPath=@Gesture}"/>
            </StackPanel>
        </HierarchicalDataTemplate>
</Window.Resources>
<Grid>
        <StackPanel>
            <Menu ItemsSource="{Binding Source={StaticResource ds}}"/>
        </StackPanel>
</Grid>

Style樣式

Style簡單來說,就是一種對屬性值的批處理,類似於Html的CSS,可以快速的設置一系列屬性值到UI元素。

Style最重要的兩個元素是Setter和Trigger,Setter類設置控件的靜態外觀風格,Trigger類設置控件的行為風格。

Style和Template就如同化妝和整容,Style可以為某類控件設置統一的樣式,如果不想使用該樣式使用{x:Null}就可清空Style.

Setter設置器

Setter設置器的兩個重要元素是Property和Value,Property屬性用來指明你想為那個目標的那個屬性賦值;Value屬性則是你提供的屬性值。

<Window.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="FontSize" Value="24"/>
            <Setter Property="TextDecorations" Value="Underline"/>
            <Setter Property="FontStyle" Value="Italic"/>
        </Style>
</Window.Resources>
<StackPanel Margin="5">
        <TextBlock Text="你好"/>
        <TextBlock Text="這是設置好的樣式"/>
        <TextBlock Text="沒有風格" Style="{x:Null}"/>
</StackPanel>

Trigger觸發器

Trigger,觸發器,即當某些條件滿足時會觸發一個行為(比如某些值的變化或動畫的發生等)。觸發器比較像事件。事件一般是由用戶操作觸發的,而觸發器除了由事件觸發的EventTrigger外還有數據變化觸發型的Trigger、DataTrigger及多條件觸發型的MultiTrigger、MultiDataTrigger等。

基本Trigger

Trigger類是最基本的觸發器,類似於Setter,Trigger也有Property和Value這兩個屬性,Property是Trigger關注的屬性名稱,Value是觸發條件。Trigger還有一個Setters屬性,此屬性值是一組Setter,一旦觸發條件被滿足,這組Seteer的“屬性-值”就會被應用,觸發條件不再滿足后,各屬性值會被還原。

示例:

CheckBox的Style,當IsChecked屬性為true時,前景色和字體變化。

<Window.Resources>
        <Style TargetType="CheckBox">
            <Style.Triggers>
                <Trigger Property="IsChecked" Value="True">
                    <Trigger.Setters>
                        <Setter Property="FontSize" Value="20"/>
                        <Setter Property="Foreground" Value="Orange"/>
                    </Trigger.Setters>
                </Trigger>
            </Style.Triggers>
        </Style>
</Window.Resources>
<Grid>
        <StackPanel>
            <CheckBox Content="悄悄的我走了" Margin="5"/>
            <CheckBox Content="悄悄的我走了" Margin="5"/>
            <CheckBox Content="悄悄的我走了" Margin="5"/>
            <CheckBox Content="悄悄的我走了" Margin="5"/>
            <CheckBox Content="悄悄的我走了" Margin="5"/>
        </StackPanel>
</Grid>

MultiTrigger

MultiTrigger必須多個條件同時成立才會被觸發,MultiTrigger比Trigger多了一個Conditions屬性,需要同時成立的條件就存儲在這個集合中。

示例:同時滿足CheckBox被選中且選中為“吃飯”時才會被觸發。

<Window.Resources>
        <Style TargetType="CheckBox">
            <Style.Triggers>
                <MultiTrigger>
                    <MultiTrigger.Conditions>
                        <Condition Property="IsChecked" Value="true" />
                        <Condition Property="Content" Value="吃飯"/>
                    </MultiTrigger.Conditions>
                    <MultiTrigger.Setters>
                        <Setter Property="FontSize" Value="20"/>
                        <Setter Property="Foreground" Value="Orange"/>
                    </MultiTrigger.Setters>
                </MultiTrigger>
            </Style.Triggers>
        </Style>
</Window.Resources>
<Grid>
        <StackPanel>
            <CheckBox Content="吃飯"/>
            <CheckBox Content="睡覺"/>
            <CheckBox Content="打豆豆"/>
            <CheckBox Content="用四川話說"/>
        </StackPanel>
</Grid>

由數據觸發DataTrigger

DataTrigger,基於數據執行某些判斷,DataTrigger對象的Binding屬性會源源不斷送過來,一旦送過來的值與Value屬性一致,DataTrigger即被觸發。

示例:當TextBox的Text長度小於7個字符時其Border會保持紅色

<Window.Resources>
        <local:L2BConverter x:Key="cvtr"/>
        <Style TargetType="TextBox">
            <Style.Triggers>
                <DataTrigger Value="false"
                    Binding="{Binding RelativeSource={x:Static RelativeSource.Self},Path=Text.Length,Converter={StaticResource cvtr}}">
                    <Setter Property="BorderBrush" Value="Red"/>
                    <Setter Property="BorderThickness" Value="1"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
</Window.Resources>
<StackPanel>
        <TextBox Margin="5"/>
        <TextBox Margin="5,0"/>
        <TextBox Margin="5"/>
</StackPanel>

這個例子中唯一需要解釋的就是DataTrigger的Binding,為了將自己作為數據源,使用了RelativeSource,初學者經常認為“不明確指出Source的值Binding就會將控件自己作為數據的來源”,這是錯誤的,因為不明確指出Source時Binding會把控件的DataContext屬性當作數據源而非把控件自身當作數據源。Binding的Path被設置為Text.Lenght,即我們關注的是字符串的長度,長度是一個具體的數字,如何基於這個長度值做判斷呢?這就用到了Converter。我們創建如下的Converter:

class L2BConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            int textLenght = (int)value;
            return textLenght > 6 ? true : false;
        }
        public object ConvertBack(object value,Type targetType,object parmeter,CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

多條數據條件觸發的MultiDataTrigger

示例:用戶界面上使用ListBox顯示了一列Student數據,當Student對象同時滿足ID為2、Name為張三的時候,條目就會高亮顯示。

<Window.Resources>
        <Style TargetType="ListBoxItem">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding ID}" Width="60"/>
                            <TextBlock Text="{Binding Name}" Width="120"/>
                            <TextBlock Text="{Binding Age}" Width="60"/>
                        </StackPanel>
                    </DataTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Binding="{Binding Path=ID}" Value="2"/>
                        <Condition Binding="{Binding Path=Name}" Value="張三"/>
                    </MultiDataTrigger.Conditions>
                    <MultiDataTrigger.Setters>
                        <Setter Property="Background" Value="Orange"/>
                    </MultiDataTrigger.Setters>
                </MultiDataTrigger>
            </Style.Triggers>
        </Style>
</Window.Resources>
<StackPanel>
        <ListBox x:Name="listBoxStudent" Margin="5"/>
</StackPanel>
public class Student
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public List<Student> Students { get; set; }
}

public MainWindow()
{
    InitializeComponent();
    Student st = new Student();
    List<Student> students = new List<Student>();
    students.Add(new Student() { ID = 1, Name = "張三", Age = 20 });
    students.Add(new Student() { ID = 2, Name = "張三", Age = 20 });
    students.Add(new Student() { ID = 3, Name = "張三", Age = 20 });
    listBoxStudent.Items.Clear();
    listBoxStudent.ItemsSource = students;
}

由事件觸發的EventTrigger

EventTrigger是觸發器中最特殊的一個。首先,它不是由屬性值或數據的變化來觸發而是由事件來觸發;其次被觸發后它並非應用一組Setter,而是執行一段動畫。因此UI層的動畫效果往往與EventTrigger相關聯。

示例:創建一個針對Button的Style,這個Style包含兩個EventTrigger,一個由MouseEnter事件觸發,另外一個由MouseLeave事件觸發。

<Window.Resources>
        <Style TargetType="Button">
            <Style.Triggers>
                <EventTrigger RoutedEvent="MouseEnter">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Width"/>
                            <DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Height"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
                <EventTrigger RoutedEvent="MouseLeave">
                    <BeginStoryboard>
                        <Storyboard>
                            <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Width"/>
                            <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Height"/>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Style.Triggers>
        </Style>
</Window.Resources>
<Canvas>
        <Button Width="40" Height="40" Content="OK"/>
</Canvas>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM