WPF 模板總結

【君莫笑】發表於2024-09-20
模板(Template): WPF系統不但支援傳統的Winfrom程式設計的使用者介面和使用者體驗設計,更支援使用專門的設計工具Blend進行專業設計,同時還推出了以模板為核心的新一代設計理念。 
在WPF中,透過引入模板(Template)微軟將資料和演算法的“內容”與“形式”解耦了。模板是演算法和資料的外衣,決定了它們長什麼樣子。 WPF中的模板(Template)分為兩大類: ControlTemplate是演算法內容的表現形式,一個控制元件怎樣組織其內部結構才能更符合業務邏輯和需求。 DataTemplate是資料內容表現形式,決定一條資料顯示成什麼樣子。 Style(樣式):設定控制元件樣式,構成Style重要的兩個元素是Setter和Trigger,Setter類幫助我們設定控制元件的靜態外觀風格,Trigger類則幫助我們設定控制元件的行為風格。 Template與Style聯絡和區別:如果只需對控制元件進行小幅度修飾(調整大小、位置、字型、顏色等)就用style,如果需要改變控制元件的外觀和行為就用controlTemplate(形狀、事件觸發如滑鼠停留效果等)。
在實際專案中,經常把Template定義在Style中,透過Style 中的Property來設定控制元件的Template屬性。
ControlTemplate 控制元件模板主要有兩個重要屬性:VisualTree內容屬性和Triggers觸發器。所謂VisualTree(視覺樹),就是呈現我們所畫的控制元件。Triggers可以對我們的視覺樹上的元素進行一些變化。
一般用於單內容控制元件。如設定一個圓角Button的ControlTemplate例項如下:

Note:

Style設定Key值,控制元件引用其Key來設定自身樣式;如果Style沒有設定Key值,則Style作用域內所有TargetType型別控制元件都預設使用其樣式。
ControlPresenter 通常叫做內容佔位符,用來替換ContentControl控制元件。如果沒有ControlPresenter ,內容控制元件就沒有內容顯示(Button上的字將不顯示)。
ItemsPresenter用於顯示條目資料,作為條目內容佔位符。 DataTemplate 允許定製.NET物件的外觀,也就是資料的外觀,常用在以下3處:ContentControl的ContentTemplate屬性,用於定製ContentControl內容的外觀;
ItemsControl的ItemTemplate屬性,用於定製ItmsControl資料條目的外觀;GridViewColumn的CellTemplate屬性,相當於定製GridViewColumn單元格資料的外觀。
1.ContentTemplate使用:

<Window x:Class="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:MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="150" Width="360">
    <Window.Resources>
        <DataTemplate x:Key="template1">
            <TextBlock Text="{Binding}" FontSize="12" FontWeight="Bold" TextWrapping="Wrap"></TextBlock>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ContentControl Name="contCtrl" ContentTemplate="{StaticResource template1}" 
                        Content="This is the content of the content control."/>
    </Grid>
</Window>
2.ItemTemplate使用:

後端程式碼:

using System;
using System.Collections.Generic;
using System.Windows;
namespace WpfTemplate
{
    /// <summary>
    /// DataTemplateDemo.xaml 的互動邏輯
    /// </summary>
    public partial class DataTemplateDemo : Window
    {
        public List<Book> BookList { get; set; } = new List<Book>();
        public DataTemplateDemo()
        {
            InitializeComponent();
            BookList.Add(new Book() { Title = "三國演義", Author = "羅貫中",Time=DateTime.Now.AddYears(-200) });
            BookList.Add(new Book() { Title = "紅樓夢", Author = "曹雪芹", Time = DateTime.Now.AddYears(-150) });
            BookList.Add(new Book() { Title = "西遊記", Author = "吳承恩", Time = DateTime.Now.AddYears(-230) });
        }
    }
}
前端程式碼:

<Window x:Class="WpfTemplate.DataTemplateDemo"
        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:WpfTemplate"
        mc:Ignorable="d"
        Title="DataTemplateDemo" Name="win" Height="400" Width="600">
    <Window.Resources>
        <DataTemplate x:Key="MyDataTemplate">
            <StackPanel Orientation="Horizontal">
                <Border Background="Pink">
                    <TextBlock Text="{Binding Title}"/>
                </Border>
                <Button Content="{Binding Author}"  Cursor="Hand" Margin="10,0"/>
            </StackPanel>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ListBox Grid.Row="1" ItemsSource="{Binding BookList,ElementName=win}" ItemTemplate="{StaticResource MyDataTemplate}"/>
    </Grid>
</Window>
3.CellTemplate使用

<Window x:Class="WpfTemplate.DataTemplateDemo"
        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:WpfTemplate"
        mc:Ignorable="d"
        Title="DataTemplateDemo" Name="win" Height="400" Width="600">
    <Grid>
        <DataGrid  AutoGenerateColumns="False" ItemsSource="{Binding BookList,ElementName=win}" Grid.Row="1" Grid.Column="1">
            <DataGrid.Columns>
                <DataGridTextColumn Header="書名" Binding="{Binding Title}" />
                <DataGridTextColumn Header="作者" Binding="{Binding Author}" />
                <DataGridTemplateColumn Header="時間">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <DatePicker SelectedDate="{Binding Time}"  BorderThickness="0" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>
ItemsPanelTemplate使用: 
        ItemsPanelTemplate可以被設定為ItemsControl的ItemsPanel。例中,ItemsControl的條目顯示預設是垂直排列,透過ItemsPanel屬性修改為水平排列。

<Grid>
    <ListBox>
        <!--ItemsPanel-->
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
         <!--條目-->
        <TextBlock Text="Allan"/>
        <TextBlock Text="Kevin"/>
        <TextBlock Text="Drew"/>
        <TextBlock Text="Timothy"/>
    </ListBox>
</Grid>
Template擴充套件:

       很多時候資料是以XML形式儲存的,DataTemplate具有直接把XML資料結點當作目標物件的功能——XML資料中的元素名(標籤名)可以作為DataType,元素的子結點和Attribute可以使用XPath來訪問。下面的程式碼使用XmlDataProvider作為資料來源,程式碼如下:

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        xmlns:local="clr-namespace:WpfApp"
        xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
        Title="MainWindow" Height="500" Width="333.035">
    <Window.Resources>
        <!--Data Template-->
        <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.0"/>
                </StackPanel>
            </Grid>
        </DataTemplate>
        <!--資料來源-->
        <XmlDataProvider x:Key="ds" XPath="Units/Unit">
            <x:XData>
                <Units xmlns="">
                    <Unit Year="2001" Price="100"/>
                    <Unit Year="2001" Price="120"/>
                    <Unit Year="2001" Price="140"/>
                    <Unit Year="2001" Price="160"/>
                    <Unit Year="2001" Price="180"/>
                    <Unit Year="2001" Price="200"/>
                </Units>
            </x:XData>
        </XmlDataProvider>
    </Window.Resources>
    <StackPanel>
        <ListBox ItemsSource="{Binding Source={StaticResource ds}}"/>
        <ComboBox ItemsSource="{Binding Source={StaticResource ds}}" Margin="5"/>
    </StackPanel>
</Window>
顯示層級資料的模板HierarchicalDataTemplate
        XML最大的優勢是可以方便地表示帶有層級的資料,WPF準備了TreeView和Menultem控制元件用來顯示層級資料,能夠幫助層級控制元件顯示層級資料的模板是HierarchicalDataTemplate。第一個例子是使用TreeView顯示多層級、不同型別資料,需要為每種資料設計一個模板,有機會使每種資料型別有自己獨特的外觀。資料儲存在專案根目錄的Data.xml檔案中,內容如下:

<?xml version="1.0" encoding="utf-8" ?>
<Data xmlns="">
  <Grade Name="一年級">
    <Class Name="甲班">         
      <Group Name="A組"/>
      <Group Name="B組"/>
      <Group Name="C組"/>
    </Class>
  <Class Name="乙班">
    <Group Name="A組"/>
    <Group Name="B組"/>
    <Group Name="C組"/>
  </Class>
  </Grade>
  <Grade Name="二年級">
    <Class Name="甲班">
      <Group Name="A組"/>
      <Group Name="B組"/>
      <Group Name="C組"/>
    </Class>
    <Class Name="乙班">
      <Group Name="A組"/>
      <Group Name="B組"/>
      <Group Name="C組"/>
    </Class>
  </Grade>
</Data>
程式的XAML程式碼如下:

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         
        Title="MainWindow" Height="400" Width="333.035">    
    <Window.Resources>
        <!--資料來源-->
        <XmlDataProvider x:Key="ds" Source="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>
</Window>

第二個例子是同一種資料型別的巢狀結構,這種情況下只需設計一個HierarchicalDataTemplate,它會產生自動迭代應用的效果。資料仍然存放在Data.xml檔案中,資料全都是Operation型別:

<?xml version="1.0" encoding="utf-8" ?>
<Data xmlns="">
  <Operation Name="檔案" Gesture="F">
    <Operation Name="新建" Gesture="N">
      <Operation Name="專案" Gesture="Control+P"/>
      <Operation Name="網站" Gesture="Control+W"/>
      <Operation Name="文件" Gesture="Control+D"/>
    </Operation>
    <Operation Name="儲存" Gesture="S"/>
    <Operation Name="列印" Gesture="P"/>
    <Operation Name="退出" Gesture="X"/>
  </Operation>
  <Operation Name="編輯" Gesture="E">
    <Operation Name="複製" Gesture="Control+C"/>
    <Operation Name="剪下" Gesture="Control+X"/>
    <Operation Name="貼上" Gesture="Control+V"/>
  </Operation>
</Data>
 程式的XAML程式碼如下:

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="300" Width="300">
    <Window.Resources>
        <!--資料來源-->
        <XmlDataProvider x:Key="ds" Source="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>
    <StackPanel MenuItem.Click="StackPanel_Click">
        <Menu ItemsSource="{Binding Source={StaticResource ds}}"/>
    </StackPanel>
</Window>
HierarchicalDataTemplate的作用目標是Menultem的Header,可以從被單擊Menultem的Header中取出XML資料。事件處理器程式碼如下:

private void StackPanel_Click(object sender, RoutedEventArgs e)
{
    MenuItem mi = e.OriginalSource as MenuItem; 
    XmlElement xe = mi.Header as XmlElement; 
    MessageBox.Show(xe.Attributes["Name"].Value);
}

 Note:可以維護一個CommandHelper類,根據拿到的資料來決定執行什麼RoutedCommand。

從外界訪問Template內部的控制元件及其屬性值
        由ControlTemplate或DataTemplate生成的控制元件都是“由Template生成的控制元件”,ControlTemplate和DataTemplate兩個類均派生自FrameworkTemplate類,有個名為FindName的方法可以檢索其內部控制元件。
檢索ControlTemplate生成的控制元件
設計一個ControlTemplate並把它應用在一個UserControl上,介面上還有一個Button,在它的Click事件處理器中檢索由ControlTemplate生成的程式碼。
程式的XAML程式碼如下:

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Title="MainWindow" Height="160" Width="300">
    <Window.Resources>
        <ControlTemplate x:Key="cTmp">
            <StackPanel Background="Orange">
                <TextBox x:Name="textBox1" Margin="6"/>
                <TextBox x:Name="textBox2" Margin="6,0"/>
                <TextBox x:Name="textBox3" Margin="6"/>
            </StackPanel>                  
        </ControlTemplate>
    </Window.Resources>
    <StackPanel Background="Yellow">
        <UserControl x:Name="uc" Template="{ StaticResource cTmp}" Margin="5"/> 
        <Button Content="Find by Name" Width="120" Height="30" Click="Button_Click"/>
    </StackPanel>
</Window>
後臺程式碼如下:

 private void Button_Click(object sender, RoutedEventArgs e)
 {
     TextBox tb = this.uc.Template.FindName("textBox1", this.uc) as TextBox;
     tb.Text = "Hello WPF";
     StackPanel sp = tb.Parent as StackPanel;
     (sp.Children[1] as TextBox).Text = "Hello ControlTemplate,I can find you!";
 }
Trigger擴充套件:
由資料觸發的DataTrigger
基於資料執行某些判斷可以考慮使用DataTrigger,DataTrigger物件的Binding屬性會把資料來源源不斷送過來,一旦送來的值與Value屬性一致DataTrigger即被觸發。
下面例子中,當TextBox的Text長度小於7個字元時其Border會保持紅色,XAML程式碼如下:
 

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    
        xmlns:local="clr-namespace:WpfApp"
        Title="MainWindow" Height="123.435" Width="204.297">
    <Window.Resources>
        <local:L2BConverter  x:Key="cvtr"/>
        <Style TargetType="TextBox">
            <Style.Triggers>
                <DataTrigger Binding="{ Binding RelativeSource={x:Static RelativeSource.Self}, Path=Text.Length, Converter={ StaticResource cvtr}}" Value="false">
                    <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>
 為了將控制元件自己作為資料來源需要使用RelativeSource,如果不明確指出Source時Binding會把控制元件的DataContext屬性當作資料來源而非把控制元件自身當作資料來源。
Binding的Path被設定為Text.Length,字串的長度是一個具體的數字,基於這個長度值做判斷時需要用到Converter,建立如下的Converter:

//經Converter轉換後,長度值會轉換成bool型別值,DataTrigger的Value被設定為false。
public class L2BConverter : IValueConverter 
{ 
    public object Convert(object value,Type targetype,object parameter, CultureInfo culture)
    {
        int textLength = (int)value; 
        return textLength > 6 ? true : false;
    }
    public object ConvertBack(object value, Type targetype, object parameter, CultureInfo culture) 
    {
        throw new NotImplementedException();
    }
}

多資料條件觸發的MultiDataTrigger
        遇到要求多個資料條件同時滿足時才能觸發變化的需求,此時可以考慮使用MultiDataTrigger。
使用者介面上使用ListBox顯示了一列Student資料,當Student物件同時滿足ID為2、Name為Tom的時候條目就高亮顯示,示例的XAML程式碼如下:

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         
        Title="MainWindow" Height="123.435" Width="300">
    <Window.Resources>
        <Style TargetType="ListBoxItem">
            <!--使用Style設定DataTemplate-->
            <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>
            <!--MultiDataTrigger-->
            <Style.Triggers>
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Binding="{Binding Path=ID}" Value="2"/>
                        <Condition Binding="{Binding Path=Name}" Value="Tom"/>
                    </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>
</Window>
後臺程式碼:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
 
        List<Student> studentList = new List<Student>() 
        {new Student(){ ID=1, Name="Tim", Age=21},
        new Student(){ ID=2, Name="Tom", Age=22 }}; 
        
        this.listBoxStudent.ItemsSource = studentList;
    } 
}
public class Student
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

由事件觸發的EventTrigger
EventTrigger是觸發器中最特殊的一個,它是由事件來觸發,被觸發後執行一段動畫,U1層的動畫效果往往與EventTrigger相關聯。
建立了一個針對Button的Style,這個Style包含兩個EventTrigger,一個由MouseEnter事件觸發,另一個由MouseLeave事件觸發。XAML程式碼如下:

        <!--由事件觸發的EventTrigger-->
        <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>

來源:https://blog.csdn.net/lvxingzhe3/article/details/129941884

相關文章