模板(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