圖形用戶界面應用程序較之控制檯界面應用程序最大的好處就是界面友好、數據顯示直觀。CUI程序中數據只能以文本的形式線性顯示,GUI程序則容許數據以文本、列表、圖形等多種形式立體顯示。html
用戶體驗在GUI程序設計中起着舉足輕重的做用-----用戶界面設計成什麼樣看上去才足夠的漂亮?控件如何安排才簡單易用而且少犯錯誤?這些都是設計師須要考慮的問題。WPF系統不但支持傳統的Winfrom編程的用戶界面和用戶體驗設計,更支持使用專門的設計工具Blend進行專業設計,同時還推出了以模板爲核心的新一代設計理念。程序員
1.1 模板的內涵算法
從字面上看,模板就是「具備必定規格的樣板」,有了它,就能夠依照它製造不少同樣是實例。咱們常把看起來同樣的東西稱爲「一個模子裏面刻出來的。」就是這個道理。然而,WPF中的模板的內涵遠比這個深入。express
Binding和基於Binding數據驅動UI是WPF的核心部分,WPF最精彩的部分是什麼呢?依我看,既不是美輪美奐的3D圖形,也不是炫目多彩的動畫,而是默默無聞的模板(Template)。實際上,就連2D/3D繪圖也經常是爲它錦上添花。編程
Templdate究竟有什麼能力可以使得它在WPF體系中獲此殊榮呢?這還要從哲學談起,「形而上者謂之道,形而下者謂之器」,這句話出自《易經》,大意是咱們可以觀察到的世間萬物形象之上的抽象的結果就是思惟,而形象之下掩蓋的就是其本質。顯然,古人已經注意到「形」是鏈接本質和思惟的樞紐,讓咱們把這句話引入計算機世界。c#
- 「形而上者謂之道」指的就是基於現實世界對萬物進行抽象封裝,理順它們之間的關係,這個「道」不就是面向對象思想嗎!若是再把面向對象進一步提高、總結出最優的對象組合關係,「道」就上升爲設計模式思想。
- 「形而下者謂之氣」指的是咱們可以觀察到的世間萬物都是物質類容的本質表現形式。「本質與表現」或者說「類容與形式」是哲學範疇內的一對矛盾體。
軟件之道並不是本書研究的主要類容,本書研究的是WPF。WPF全稱Windows Presentation Foundation,Presentation一詞的意思就是外觀,呈現,表現,也就是說,在WIndows GUI程序這個尺度上,WPF扮演的就是「形」的角色、是程序的外在形式,而程序的內容仍然是由數據和算法構成的業務邏輯。與WPF相似,Winform和Asp.net也都是內容的表現形式。設計模式
![](http://static.javashuo.com/static/loading.gif)
讓咱們把尺度縮小到WPF內部。這個系統與程序內容(業務邏輯)的邊界是Binding,Binding把數據源源不斷從程序內部送出來交由界面元素來顯示,又把從界面元素蒐集到的數據傳回程序內部。界面元素間的溝通則依靠路由事件來完成。有時候路由事件和附加事件也會參與到數據的傳輸中。讓咱們思考一個問題:WPF做爲Windows的表示方式,它究竟表示的是什麼?換句話說,WPF做爲一種「形式」,它表現的內容究竟是什麼?答案是程序的數據和算法----Binding傳遞的是數據,事件參數攜帶的也是數據;方法和委託的調用是算法,事件傳遞消息也是算法----數據在內存裏就是一串串字符或字符。算法是一組組看不見摸不着的抽象邏輯,如何恰如其分的把它們展示給用戶呢?app
加入想表達一個bool類型,同時還想表達用戶能夠在這兩個值之間自由切換這樣一個算法,你會怎麼作?你必定會想使用一個CheckBox控件來知足要求;再好比顏色值其實是一串數字,用戶基本上不可能只看數字就能想象出真正的顏色,並且用戶也不但願只靠輸入字符來表示顏色值,這時,顏色值這一「數據內容」的恰當表現形式就是一個填充着真實顏色的色塊。,而用戶便可以輸入值又能夠用取色吸管取色來設置值的「算法內容」恰當的表達方式是建立一個ColorPicker控件。相信你已經發現,控件(Control)是數據內容表現形式的雙重載體。換句話說,控件便是數據的表現形式讓用戶能夠直觀的看到數據,又是算法的表現形式讓用戶方便的操做邏輯。ide
做爲表現形式,每一個控件都是爲了實現某種用戶操做算法和直觀顯示某種數據而生,一個控件看上去是什麼樣子由它的「算法內容」和「數據內容決定」,這就是內容決定形式,這裏,咱們引入兩個概念:工具
- 控件的算法內容:值控件能展現哪些數據、具備哪些方法、能相應哪些操做、能激發什麼事件,簡而言之就是控件的功能,它們是一組相關的算法邏輯。
- 控件的數據內容:控件具體展現的數據是什麼。
以往的GUI開發技術(ASP.NET+Winform)中,控件內部邏輯和數據是固定的,程序員不能改變;對於控件的外觀,程序員能作的改變也很是的有限,通常也就是設置控件的屬性,想改變控件的內部結構是不可能的。若是想擴展一個控件的功能或者更改器外觀讓其更適應業務邏輯,哪怕只是一丁點的改變,也須要建立控件的子類或者建立用戶控件。形成這個局面的根本緣由是數據和算法的「形式」和「內容」耦合的太緊了。
在WPF中,經過引入模板微軟將數據和算法的內容與形式接耦合了。WPF中的Template分爲兩大類:
- ControlTemplate:是算法和內容的表現形式,一個控件怎麼組織其內部結構才能讓它更符合業務邏輯、讓用戶操做起來更舒服就是由它來控制的。它決定了控件「長成什麼樣子」,並讓程序員有機會在控件原有的內部邏輯基礎上擴展本身的邏輯。
- DataTemplate:是數據內容的展現方式,一條數據顯示成什麼樣子,是簡單的文本仍是直觀的圖形就由它來決定了。
Template就是數據的外衣-----ControlTemplate是控件的外衣,DataTemplate是數據的外衣。
下面讓咱們欣賞兩個例子:
WPF中控件不在具備固定的形象,僅僅是算法內容或數據內容的載體。你能夠把控件理解爲一組操做邏輯穿上了一套衣服,換套衣服就變成了另一個模樣。你看到的控件默認形象實際上就是出廠時微軟爲它穿上的默認衣服。看到下面圖中的溫度計,你是否是習慣性的猜到是由若干控件和圖形拼湊起來的UserControl呢?實際上它是一個ProgressBar控件,只是咱們的設計師爲其設計了一套新衣服-----這套衣服改變了其一些顏色、添加了一些裝飾品和刻度線並清除了脈搏動畫,效果以下圖:
WPF中數據顯示成什麼樣子能夠由本身來決定。好比下面這張圖,只是爲數據條目準備了一個DataTemplate,這個DataTemplate中用binding把一個TextBlock的Text屬性值關聯到數據對象的Year屬性上、把一個Rectangle的Width屬性和另一個TextBlock的Text屬性關聯到數據對象的Price屬性上,並使用StackPanel和Grid爲這幾個控件佈局。一旦應用了這個DataTemplate,單調的數據就變成了直觀的柱狀圖,以下圖所示。以往這項工做不但須要先建立用於展現數據的UserControl,還要爲UserControl添加顯示/回寫數據的代碼。
若是別的項目中也須要用到這個柱狀圖,你要作的事情只是將這個XAML代碼發給他們。其代碼以下:
- <DataTemplate>
- <Grid>
- <StackPanel Orientation="Horizontal">
- <Grid>
- <Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding Price}"></Rectangle>
- <TextBlock Text="{Binding Year}"></TextBlock>
- </Grid>
- <TextBlock Text="{Binding Price}" Margin="5,0"></TextBlock>
- </StackPanel>
- </Grid>
- </DataTemplate>
我想,儘管你尚未學習什麼DataTempldate,但藉助前面學習的基礎同樣能夠看個八九不離十了。
1.2 數據的外衣DataTemplate
「橫當作嶺側成峯,遠近高低各不一樣」廬山的美景如此,數據又未嘗不是這樣呢?一樣一條數據,好比具備ID、Name、PhoneNumber、Address等Student的實例,放在GridView裏面有時多是簡單的文本、每一個單元格只顯示一個屬性;放在ListBox裏面有時爲了不單調能夠在最左端顯示一個64*64的小圖像,再將其它信息分兩行顯示在其後面;若是單獨顯示一個學生信息則能夠用相似簡歷的複雜格式來展示學生的所有數據。同樣的內容能夠用不一樣的形式來展示,軟件設計稱之爲「數據--視圖」模式。以往的開發技術,如MFC、Winform、Asp.net等,視圖要靠UserControl來實現。WPF不但支持UserControl還支持DataTemplate爲數據造成視圖。不要覺得DataTempldate有多難!從Control升級到DataTemplate通常就是複製,粘貼一下再改幾個字符的事兒。
DataTempldate經常使用的地方有三處,分別是:
- ContentControl的ContentTempldate屬性,至關於給ContentControl的內容穿衣服。
- ItemControl的ItemTemplate,至關於給ItemControl的數據條目穿衣服。
- GridViewColumn的CellTempldate屬性,至關於給GridViewColumn的數據條目穿衣服。
讓咱們用一個例子對比UserControl和DataTemplate的使用。例子實現的需求是這樣的:有一列汽車數據,這列數據顯示在ListBox裏面,要求ListBox的條目顯示汽車的廠商圖標和簡要參數,單擊某個條目後在窗體的詳細內容區顯示汽車的圖片和詳細參數。
不管是使用UserControl仍是DataTemplate,廠商的Logo和汽車的照片都是要用到的,因此先在項目中創建資源管理目錄並把圖片添加進來。Logo文件名與廠商的名稱一致,照片的名稱則與車名一致。組織結構如圖:
首先建立Car數據類型:
- public class Car
- {
- public string AutoMark { get; set; }
- public string Name { get; set; }
- public string Year { get; set; }
- public string TopSpeed { get; set; }
- }
爲了在ListBox裏面顯示Car類型的數據,咱們須要準備一個UserControl。命名爲CarListItemView。
這個UserControl由一個Car類型實例在背後支持,當設置這個實例的時候,界面元素將實例的屬性值顯示在各個控件裏。CarListItemView的XAML代碼以下:
- <UserControl x:Class="WpfApplication1.CarListViewItem"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
- <Grid Margin="2">
- <StackPanel Orientation="Horizontal">
- <Image x:Name="igLogo" Grid.RowSpan="3" Width="64" Height="64"></Image>
- <StackPanel Margin="5,10">
- <TextBlock x:Name="txtBlockName" FontSize="16" FontWeight="Bold"></TextBlock>
- <TextBlock x:Name="txtBlockYear" FontSize="14"></TextBlock>
- </StackPanel>
- </StackPanel>
- </Grid>
- </UserControl>
CarlistItemView用於支持前臺顯示屬性C#代碼爲:
- public partial class CarListViewItem : UserControl
- {
- public CarListViewItem()
- {
- InitializeComponent();
- }
-
- private Car car;
-
- public Car Car
- {
- get { return car; }
- set
- {
- car = value;
- this.txtBlockName.Text = car.Name;
- this.txtBlockYear.Text = car.Year;
- this.igLogo.Source = new BitmapImage(new Uri(@"Resource/Image/"+car.AutoMark+".png",UriKind.Relative));
- }
- }
- }
相似的原理,咱們須要爲Car類型準備一個詳細信息視圖。UserControl名稱爲CarDetailView,XAML部分代碼以下:
- <UserControl x:Class="WpfApplication1.CarDetailView"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
- <Border BorderBrush="Black" BorderThickness="1" CornerRadius="6">
- <StackPanel>
- <Image x:Name="imgPhoto" Width="400" Height="250"></Image>
- <StackPanel Orientation="Horizontal" Margin="5,0">
- <TextBlock Text="Name:" FontSize="20" FontWeight="Bold"></TextBlock>
- <TextBlock x:Name="txtBlockName" FontSize="20" Margin="5,0"></TextBlock>
- </StackPanel>
- <StackPanel Orientation="Horizontal" Margin="5,0">
- <TextBlock Text="AutoMark:" FontWeight="Bold"></TextBlock>
- <TextBlock x:Name="txtBlockAutoMark" Margin="5,0"></TextBlock>
- <TextBlock Text="Year:" FontWeight="Bold">
-
- </TextBlock>
- <TextBlock x:Name="txtBlockYear" Margin="5,0">
-
- </TextBlock>
- <TextBlock Text="Top Speed:" FontWeight="Bold">
-
- </TextBlock>
- <TextBlock x:Name="txtTopSpeed" Margin="5,0">
-
- </TextBlock>
- </StackPanel>
- </StackPanel>
- </Border>
- </UserControl>
後臺支持數據大同小異:
- public partial class CarDetailView : UserControl
- {
- public CarDetailView()
- {
- InitializeComponent();
- }
-
- private Car car;
-
- public Car Car
- {
- get { return car; }
- set
- {
- car = value;
- this.txtBlockName.Text = car.Name;
- this.txtBlockAutoMark.Text = car.AutoMark;
- this.txtBlockYear.Text = car.Year;
- this.txtTopSpeed.Text = car.TopSpeed;
- this.imgPhoto.Source = new BitmapImage(new Uri(@"Resource/Image/" + car.Name + ".jpg", UriKind.Relative));
- }
- }
- }
最後把它們組裝到窗體上:
- <Window x:Class="WpfApplication1.Window35"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window35" Height="350" Width="623" xmlns:my="clr-namespace:WpfApplication1">
- <StackPanel Orientation="Horizontal" Margin="5">
- <my:CarDetailView x:Name="carDetailView1" />
- <ListBox x:Name="listBoxCars" Width="180" Margin="5,0" SelectionChanged="listBoxCars_SelectionChanged">
-
- </ListBox>
- </StackPanel>
- </Window>
窗體的後臺代碼以下:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Data;
- using System.Windows.Documents;
- using System.Windows.Input;
- using System.Windows.Media;
- using System.Windows.Media.Imaging;
- using System.Windows.Shapes;
-
- namespace WpfApplication1
- {
-
-
-
- public partial class Window35 : Window
- {
- public Window35()
- {
- InitializeComponent();
- InitialCarList();
- }
-
- private void listBoxCars_SelectionChanged(object sender, SelectionChangedEventArgs e)
- {
- CarListViewItem viewItem = e.AddedItems[0] as CarListViewItem;
- if(viewItem!=null)
- {
- carDetailView1.Car = viewItem.Car;
- }
- }
-
- private void InitialCarList()
- {
- List<Car> infos = new List<Car>() {
- new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="200", Year="1990"},
- new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="250", Year="1998"},
- new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="300", Year="2002"},
- new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="350", Year="2011"},
- new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="500", Year="2020"}
- };
- foreach (Car item in infos)
- {
- CarListViewItem viewItem = new CarListViewItem();
- viewItem.Car = item;
- this.listBoxCars.Items.Add(viewItem);
- }
- }
- }
-
- public class Car
- {
- public string AutoMark { get; set; }
- public string Name { get; set; }
- public string Year { get; set; }
- public string TopSpeed { get; set; }
- }
- }
運行並單擊Item項,運行效果以下圖:
很難說這樣作是錯的,可是WPF裏面如此實現需求真的是浪費了數據驅動界面這一重要功能。咱們常說把WPF看成Winform來用指的就是這種實現方法。這種作法對WPF最大的曲解就是沒有藉助Binding來實現數據驅動界面,而且認爲ListBoxItem裏面放置的控件---這種曲解迫使數據在界面元數據間交換而且程序員只能經過事件驅動方式來實現邏輯------程序員必須藉助處理ListBox的SelecttionChanged事件來推進DetaIlView來顯示數據,而數據又是由CarListItemView控件轉交給CarDetailView的,之間還作了一次類型轉換。下圖用於說明事件驅動模式與指望中數據驅動界面模式的不一樣:
顯然,事件驅動是控件和控件之間溝通或者說是形式和形式之間的溝通,數據驅動則是數據與控件之間的溝通,是內容決定形式。使用DataTemplate就能夠方便的把事件驅動模式轉換爲數據驅動模式。
你是否是擔憂前面寫的代碼會被刪掉呢?不會的!由UserControl升級爲DataTemplate時90%的代碼是Copy,10%的代碼能夠方向刪除,再作一點點改動就能夠了。讓咱們來試試看。
首先把連個UserControl的芯剪切出來,用DataTempldate進行包裝,再放到主窗體的資源字典裏。最重要的是爲DataTemplate裏面的每個控件設置Binding,告訴各個控件應該關注的是數據的哪一個屬性。由於使用BInding在控件和數據間創建關聯,免去了在C#代碼中訪問界面元素,因此XAML代碼中的大部分x:Name均可以刪掉。代碼看上去也簡介了很多。
有些屬性不能直接拿來用,好比汽車廠商和名稱不能直接拿來作爲圖片路徑,這時就要使用Converter。有兩種辦法能夠在XAML代碼中使用Converter:
- 把Converter以資源字典的形式放進資源字典裏(本例使用的方法)。
- 爲Converter準備一個靜態屬性,造成單件模式,在XAML代碼裏面使用{x:Static}標記擴展來訪問。
咱們的兩個Converter代碼以下:
- public class AutoMarkToLogoPathConverter:IValueConverter
- {
-
-
-
-
-
-
-
-
- public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
- {
- return new BitmapImage(new Uri(string.Format(@"Resource/Image/{0}.png",(string)value),UriKind.Relative));
- }
-
-
-
-
-
-
-
-
- public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
- {
- throw new NotImplementedException();
- }
- }
- public class NameToPhotoPathConverter:IValueConverter
- {
-
-
-
-
-
-
-
-
- public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
- {
- return new BitmapImage(new Uri(string.Format(@"Resource/Image/{0}.jpg", (string)value), UriKind.Relative));
- }
-
-
-
-
-
-
-
-
- public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
- {
- throw new NotImplementedException();
- }
- }
有了這兩個Converter以後咱們就能夠設計DataTemplate了,完整的XAML代碼以下:
- <Window x:Class="WpfApplication1.Window36"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:local="clr-namespace:WpfApplication1.Model"
- Title="Window36" Height="350" Width="623">
- <Window.Resources>
-
- <local:AutoMarkToLogoPathConverter x:Key="amp"/>
- <local:NameToPhotoPathConverter x:Key="npp"/>
-
- <DataTemplate x:Key="DatialViewTemplate">
- <Border BorderBrush="Black" BorderThickness="1" CornerRadius="6">
- <StackPanel>
- <Image x:Name="imgPhoto" Width="400" Height="250" Source="{Binding AutoMark,Converter={StaticResource npp}}"></Image>
- <StackPanel Orientation="Horizontal" Margin="5,0">
- <TextBlock Text="Name:" FontSize="20" FontWeight="Bold"></TextBlock>
- <TextBlock FontSize="20" Margin="5,0" Text="{Binding Name}"></TextBlock>
- </StackPanel>
- <StackPanel Orientation="Horizontal" Margin="5,0">
- <TextBlock Text="AutoMark:" FontWeight="Bold"></TextBlock>
- <TextBlock Margin="5,0" Text="{Binding AutoMark}"></TextBlock>
- <TextBlock Text="Year:" FontWeight="Bold">
-
- </TextBlock>
- <TextBlock Text="{Binding Year}" Margin="5,0">
-
- </TextBlock>
- <TextBlock Text="Top Speed:" FontWeight="Bold">
-
- </TextBlock>
- <TextBlock Text="{Binding TopSpeed}" Margin="5,0">
-
- </TextBlock>
- </StackPanel>
- </StackPanel>
- </Border>
- </DataTemplate>
-
- <DataTemplate x:Key="ItemView">
- <Grid Margin="2">
- <StackPanel Orientation="Horizontal">
- <Image x:Name="igLogo" Grid.RowSpan="3" Width="64" Height="64" Source="{Binding Name,Converter={StaticResource amp}}"></Image>
- <StackPanel Margin="5,10">
- <TextBlock Text="{Binding Name}" FontSize="16" FontWeight="Bold"></TextBlock>
- <TextBlock Text="{Binding Year}" FontSize="14"></TextBlock>
- </StackPanel>
- </StackPanel>
- </Grid>
- </DataTemplate>
- </Window.Resources>
-
- <StackPanel Orientation="Horizontal">
- <UserControl ContentTemplate="{StaticResource DatialViewTemplate}" Content="{Binding Path=SelectedItem,ElementName=lbInfos}"></UserControl>
- <ListBox x:Name="lbInfos" ItemTemplate="{StaticResource ItemView}"></ListBox>
- </StackPanel>
- </Window>
代碼對於初學者來講有點長可是結構很是簡單。其中最重要的有兩句:
ContentTemplate="{StaticResource DatialViewTemplate}",至關於給一個普通的UserControl穿上了一件外衣、讓Car數據以圖文並茂的方式展示出來。這件外衣就是x:Key="DatialViewTemplate"標記的DataTemplate資源。
ItemTemplate="{StaticResource ItemView}",把每一件數據的外衣交給ListBox,當ListBox的ItemSource被賦值的時候,ListBox就會爲每一個條目穿上這件外衣。這件外衣是以x:Key="ItemView"標記的DataTemplate資源。
由於再也不使用事件驅動,並且爲數據穿衣服的事也已經自動完成,因此後臺的C#代碼就很是的簡單。窗體的C#代碼就只剩下這些:
- public partial class Window36 : Window
- {
- public Window36()
- {
- InitializeComponent();
- InitialCarList();
- }
-
- private void InitialCarList()
- {
- List<Car> infos = new List<Car>() {
- new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="200", Year="1990"},
- new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="250", Year="1998"},
- new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="300", Year="2002"},
- new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="350", Year="2011"},
- new Car(){ AutoMark="Aodi", Name="Aodi", TopSpeed="500", Year="2020"}
- };
- this.lbInfos.ItemsSource = infos;
- }
- }
運行程序,效果以下圖:
與以前用UserControl沒有任何區別。用戶永遠不知道程序員在後臺使用的是什麼技術與模式,可是對於程序員,咱們能夠清楚的體會到使用DataTemplate可讓程序結構更加清晰、代碼更加簡潔、維護更方便。不誇張的說,是DataTemplate幫助完全完成了「數據驅動界面」,讓Bingding和數據驅動滲透到用戶界面的每個細胞中。
1.3 控件的外衣ControlTemplate
往往提到ControlTemplate我都會想到「披着羊皮的狼」這句話-----披上羊皮以後,雖然看上去像只羊,但其行爲仍然是匹狼。狼的行爲指的是它能吃別的動物、對着滿月嚎叫等事情,控件也有本身的行爲,好比顯示數據、執行方法、激發事件等。控件的行爲要靠編程邏輯來實現,因此也能夠把控件的行爲稱爲控件的算法內容。舉個例子,WPF中的CheckBox與其基類ToggleButton的功能幾乎徹底同樣,但外觀差異上卻很是的大,這就是更換ControlTemplate的結果。通過更換ControlTemplate,咱們不但能夠製做披着CheckBox外衣的ToggleButton,還能製做披着溫度計外衣的ProgressBar控件。
注意:
實際項目中,ControlTemplate主要有兩大用武之地:
- 經過更換ControlTemplate來更換控件的外觀,使之具備更優的用戶體驗和外觀。
- 藉助ControlTemplate,程序員和設計師能夠並行工做,程序員可使用WPF標準控件進行編程,等設計師的工做完成以後,只須要把新的ControlTemplate應用的程序中便可。
如何爲控件設計ControlTemplate呢?首先須要你瞭解每一個控件的內部結構。你可能會問:在哪兒能夠查看到控件的內部結構呢?沒有文檔可查,想知道一個控件的內部結構必須把控件「打碎」了看一下。用於打碎控件、查看控件內部結構的工具就是blend,目前最新版本是5.0。
1.3.1 庖丁解牛看控件
挑柿子應該找軟的捏,剖析控件也得叢簡單的入手。TextBox和Button最簡單,咱們就從這兩個控件入手。運行Blend,新建一個項目或者打開一個已經存在的項目,先把窗體的顏色改成線性漸變,再在窗體的主區域畫兩個TextBox和一個Button。對於程序員來講,徹底能夠把Blend看作是一個功能更強大的窗體設計器,而對於設計師來講,能夠把Blend理解爲XAML代碼的PhotoShop或者FireWorks。程序運行效果以下圖:
如今的TextBox方方正正,有棱有角,與窗體和Button的圓角風格不太協調,怎麼將它的邊框變成圓角矩形呢?傳統的方法多是建立一個UserControl並在TextBox的外面套一個Border,而後還要聲明一些屬性和方法暴露封裝在UserControl裏的TextBox上。咱們的辦法是在TextBox上右擊,在彈出的菜單項裏面選擇編輯模板----編輯副本,以下圖所示:
之因此不選擇建立空項是由於建立空項須要重頭開始設計一個控件的Con't'rolTemplate,新作的衣服哪如改衣服來的快啊!單擊菜單項後彈出資源對話框,儘管能夠用C#來建立ControlTemplate,可是絕大多數狀況下ControlTemplate是由XAML代碼編寫的並放在資源詞典裏,因此纔會彈出對話框詢問你資源的x:Key是什麼、打算把資源放在哪裏。做爲資源,ControlTemplate能夠放在三個地方:Application資源詞典裏、某個界面元素的資源詞典裏、或者放在外部XAML文件中。咱們選擇把它放在Application的資源詞典裏以方便t統一管理,並命名爲RoundCornerTextBoxStyle,以下圖所示:
單擊肯定按鈕便進入了模板的編輯狀態。在對象和時間線面板中觀察已經解剖開的TextBox控件,發現它是由一個名爲Bd的ListBoxChrome套着一個名爲PART_ContentHost的ScrollViewer組成的。爲了顯示矩形的圓角邊框,咱們只須要把外層的ListBoxChrome換成Border,刪掉Border不具有的屬性值、設置它的圓角弧度便可。
更改後的核心代碼以下:
- <Style x:Key="RoundCornerTextBoxStyle" BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
- <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
- <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
- <Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
- <Setter Property="BorderThickness" Value="1"/>
- <Setter Property="Padding" Value="1"/>
- <Setter Property="AllowDrop" Value="true"/>
- <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
- <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
- <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
- <Setter Property="Template">
- <Setter.Value>
- <ControlTemplate TargetType="{x:Type TextBox}">
- <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true" CornerRadius="5">
- <ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
- </Border>
- <ControlTemplate.Triggers>
- <Trigger Property="IsEnabled" Value="false">
- <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
- <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
- </Trigger>
- </ControlTemplate.Triggers>
- </ControlTemplate>
- </Setter.Value>
- </Setter>
- </Style>
這段代碼有如下幾個看點:
看點一:做爲資源的不是單純的ControlTemplate而是Style,說是編輯ControlTemplate可是其實是吧ControlTemplate包含在Style裏,不知道微軟會不會更正這個小麻煩。Style是什麼呢?簡單講就是一組<Setter>,也就是一組屬性設計器。回想一下Winfrom編程的時候,窗體設計器不是能夠生成這樣的代碼嗎:
- //
- // button1
- //
- this.button1.Location = new System.Drawing.Point(1100, 199);
- this.button1.Name = "button1";
- this.button1.Size = new System.Drawing.Size(75, 23);
- this.button1.TabIndex = 0;
- this.button1.Text = "報表";
- this.button1.UseVisualStyleBackColor = true;
- this.button1.Click += new System.EventHandler(this.button1_Click);
- //
- // printPreviewDialog1
- //
- this.printPreviewDialog1.AutoScrollMargin = new System.Drawing.Size(0, 0);
- this.printPreviewDialog1.AutoScrollMinSize = new System.Drawing.Size(0, 0);
- this.printPreviewDialog1.ClientSize = new System.Drawing.Size(400, 300);
- this.printPreviewDialog1.Enabled = true;
- this.printPreviewDialog1.Icon = ((System.Drawing.Icon)(resources.GetObject("printPreviewDialog1.Icon")));
- this.printPreviewDialog1.Name = "printPreviewDialog1";
- this.printPreviewDialog1.Visible = false;
一樣的邏輯若是在XAML代碼裏出就變成了這樣:
- <Style x:Key="a">
- <Setter Property="pName1" Value="value"></Setter>
- <Setter Property="pName2" Value="value"></Setter>
- <Setter Property="pName3">
- <Setter.Value>
-
- </Setter.Value>
- </Setter>
- <Setter Property="pName4">
- <Setter.Value>
-
- </Setter.Value>
- </Setter>
- lt;/Style>
使用Style是,如過Value值比較簡單,那麼就直接用Attribute值表示,若是Value值不能用一個簡單的字符串來表示那麼就須要用XAML的屬性對象語法。例子中,TextBox的Template是一個ControlTemplate對象,如此複雜的值只能使用屬性對象語法來描述。對於Style,後面會有專門的章節來描述。
看點二:直接將ListBoxChrome替換成Border標籤,去掉Border不具有的屬性並添加CornerRadius=「5」;
看點三:TemplateBinding。ControlTemplate最終會被用到一個控件上,咱們稱這個控件爲模板目標控件或者模板化控件,ControlTemplate裏面的控件可使用TemplateBinding將本身的某個屬性值關聯到模板控件的某個屬性值上,必要的時候還能夠添加Converter。例如BackGround=「{TemplateBinding Background}」這句,意思是讓Border的Background和目標控件保持一致,產生的效果就是你爲模板的目標控件設置的Background屬性,Border被Background也會跟着變。回顧Binding一節,你會發現,TemplateBinding的功能和{Binding RelativeSource={RelativeSource TemplatedParent}}一致;
好了,把咱們設計好的Style應用到兩個TextBox上,代碼以下:
- <Window
- 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" mc:Ignorable="d"
- x:Class="WPFApplication.MainWindow"
- x:Name="Window"
- Title="MainWindow"
- Width="385" Height="275">
- <Window.Background>
- <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
- <GradientStop Color="#FF4D91C6" Offset="0"/>
- <GradientStop Color="#FFD9DBF1" Offset="1"/>
- </LinearGradientBrush>
- </Window.Background>
-
- <Grid x:Name="LayoutRoot">
- <TextBox HorizontalAlignment="Left" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Margin="57,49,0,0" Width="255.487" Style="{DynamicResource RoundCornerTextBoxStyle}"/>
- <TextBox TextWrapping="Wrap" Text="TextBox" Margin="57,106,56.513,109.163" d:LayoutOverrides="Height" Style="{DynamicResource RoundCornerTextBoxStyle}"/>
- <Button Content="Button" VerticalAlignment="Bottom" Margin="149,0,145,42.163"/>
- </Grid>
- </Window>
程序運行效果以下圖:
以一樣的方法打碎Button,你會發現Button的內部結構和TextBox差很少。可是若是打碎一個ProgressBar,你會發現它的內部結構就複雜多了,以下圖:
在Blend裏面你能夠經過控制控件後面的眼睛來控制控件的顯示與隱藏,這樣就能夠區分每一個子控件的用處,這也是學習控件的最好方法。若是想把這個ProgressBar改變成一個溫度計,只須要再此基礎上添加一個背景、更改控件指示器的前景色、再在合適的控件外面套一個畫出刻度Grid(刻度能夠根據要求計算出來也能夠是固定的)。
不知道你們意識到了沒有,其實每個控件自己就是一顆UI元素樹。WPF中的UI元素樹能夠看作是兩個樹-----LogicTree和VisualTree,這兩棵樹的交點就是ControlTemplate。若是把界面上的控件元素看做一個節點,那元素構成的就是LogicTree,若是把控件內部由ControTemplate生成的控件也算上,那就構成了VisualTree。換句話說,LogicTree上導航不會進入控件內部,而在VisualTree上導航則能夠檢索到控件內部由ControlTemplate生成的子級控件。
1.3.2 ItemsControl的PanelTemplate
ItemsControl具備一個名爲ItemsPanel的屬性,它的數據類型是ItemsPanelTemplate。ItemsPanelTemplate也是一種控件Template,它的作用是可讓程序員能夠控制ItemControl的條目容器。
舉例而言,在咱們的印象中ListBox中的條目都是至上而下排列的,若是客戶要求咱們作一個水平排列的ListBox怎麼辦呢?WPF以前,咱們只能重寫控件比較底層的方法和屬性,而如今咱們只須要調整ListBox的ItemPanel屬性。請看下面的代碼。
這是一個沒有通過調整的ListBox,條目縱向排列
- <Grid x:Name="LayoutRoot" Margin="5">
- <ListBox>
- <TextBlock>Darren</TextBlock>
- <TextBlock>Andy</TextBlock>
- <TextBlock>Jacky</TextBlock>
- <TextBlock>T-Soft</TextBlock>
- </ListBox>
- </Grid>
若是咱們把代碼改爲這樣:
- <Grid x:Name="LayoutRoot" Margin="5">
- <ListBox>
-
- <ListBox.ItemsPanel>
- <ItemsPanelTemplate>
- <StackPanel Orientation="Horizontal"></StackPanel>
- </ItemsPanelTemplate>
- </ListBox.ItemsPanel>
-
- <TextBlock>Darren</TextBlock>
- <TextBlock>Andy</TextBlock>
- <TextBlock>Jacky</TextBlock>
- <TextBlock>T-Soft</TextBlock>
- </ListBox>
- </Grid>
條目就會包裝在一個水平排列的StackPanel中,從而橫向排列,以下圖所示:
1.4 DataTemplate和ControlTemplate的關係和應用
1.4.1 DataTemplate和ControlTemplate的關係
學習過DataTemplate和ControlTemplate,你應該已經體會到,控件只是數據的行爲和載體,是個抽象的概念,至於它自己長成什麼樣子(控件內部結構),它的數據會長成什麼樣子(數據顯示結構)都是靠Template生成的。決定控件外觀的ControlTemplate,決定數據外觀的DataTemplate,它們正式Contro類的Template和ContentTemplate兩個屬性值。它們的做用範圍以下圖:
凡是Template,最終都要做用在控件上,這個控件就是Template的目標控件,也叫模板化控件。你可能會問:DataTemplate的目標應該是數據呀,怎麼會是控件呢。DataTemplate給人的感受的確是施加在數據對象上,但施加在數據對象上生成的一組控件總得有個載體吧?這個載體通常落實在一個叫作ContentPresenter對象上。ContentPresenter類只有ContentTemplate屬性、沒有Template屬性,這就證實了承載由DataTemplate生成的一組控件是他的專門用途。
至此咱們能夠看出,由ControlTemplate生成的控件樹其樹根就是ControleTemplate的目標控件,此模板化控件的Template屬性值就是一個ControlTemplate實例。與之相仿,由DataTemplate生成的控件樹其根是一個ContentPresenter控件,此模板化控件的ContentTemplate屬性值就是這個DataTemplate實例。由於ContentPresenter控件是ControlTemplate控件樹上的一個節點,因此DataTemplate控件樹是ControlTemplate裏面的一個子樹。它們的關係以下圖:
既然Template生成的控件樹都有根,那麼如何去找到根呢?辦法很簡單,每一個控件都有個名爲TemplateParent屬性,若是它的值不爲null,說明這個控件是由Template自動生成的,而屬性值就是應用了模板的控件。若是由Template生成的控件使用了TemplateBinding獲取屬性值,則TemplateBinding的數據源就是應用這個模板的目標控件。
回顧一下本章開頭的DataTemplate源碼:
- <DataTemplate>
- <Grid>
- <StackPanel Orientation="Horizontal">
- <Grid>
- <Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding Price}"></Rectangle>
- <TextBlock Text="{Binding Year}"></TextBlock>
- </Grid>
- <TextBlock Text="{Binding Price}" Margin="5,0"></TextBlock>
- </StackPanel>
- </Grid>
- </DataTemplate>
這裏用的普通的Binding而不是TemplateBinding,那數據源又是誰呢?不知道你們是否還記得,當爲一個Binding只指定Path而不指定Source的時候,Binding會沿邏輯樹一直向上找、查看沒一個節點的DataContext屬性,若是DataContext引用的對象具備Path指定的屬性名,Binding就會把這個對象看成本身的數據源。顯然,若是把數據對象賦值給ContentPresenter的DataContext屬性,由DataTeplate生成的控件天然會找到這個數據對象並把它看成本身的數據源。
1.4.2 DataTemplate和Control的應用
爲Template設置其應用目標有兩種方法,一個是逐個設置控件的Template/ContentTemplate/ItemTemlate/CellTemplate等屬性,不想應用Template的控件不設置;另外一種是總體應用,即把Template應用到某個類型的控件或者數據上。
把ControlTemplate應用到全部控件上須要藉助Style來實現,但Style不能標記X:KEY,例以下面的代碼:
- <Style BasedOn="{x:Null}" TargetType="{x:Type TextBox}">
- <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
- <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
- <Setter Property="BorderBrush" Value="{StaticResource TextBoxBorder}"/>
- <Setter Property="BorderThickness" Value="1"/>
- <Setter Property="Padding" Value="1"/>
- <Setter Property="AllowDrop" Value="true"/>
- <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
- <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
- <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
- <Setter Property="Template">
- <Setter.Value>
- <ControlTemplate TargetType="{x:Type TextBox}">
- <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="true" CornerRadius="5">
- <ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
- </Border>
- <ControlTemplate.Triggers>
- <Trigger Property="IsEnabled" Value="false">
- <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
- <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
- </Trigger>
- </ControlTemplate.Triggers>
- </ControlTemplate>
- </Setter.Value>
- </Setter>
- lt;/Style>
Style沒有X:key標記,默認爲引用到全部的x:type指定的控件上,若是不想應用則將style標記爲{x:null}。運行效果以下圖:
把DataTemplate應用到某個數據類型上是設置DataTemplate的DataType屬性,而且DataTemplate做爲資源時也不能帶x:key標記, 例以下面的代碼:
- <Window
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:local="clr-namespace:WPFApplication"
- xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
- x:Class="WPFApplication.Window3"
- x:Name="Window"
- Title="Window3"
- Width="288" Height="181">
- <Window.Resources>
-
- <DataTemplate DataType="{x:Type local:Unit}">
- <Grid>
- <StackPanel Orientation="Horizontal">
- <Grid>
- <Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding Price}"></Rectangle>
- <TextBlock Text="{Binding Year}"></TextBlock>
- </Grid>
- <TextBlock Text="{Binding Year}" Margin="5,0"></TextBlock>
- </StackPanel>
- </Grid>
- </DataTemplate>
-
- <c:ArrayList x:Key="ds">
- <local:Unit Year="2001年" Price="100"></local:Unit>
- <local:Unit Year="2002年" Price="120"></local:Unit>
- <local:Unit Year="2003年" Price="140"></local:Unit>
- <local:Unit Year="2004年" Price="160"></local:Unit>
- <local:Unit Year="2005年" Price="180"></local:Unit>
- <local:Unit Year="2006年" Price="200"></local:Unit>
- </c:ArrayList>
-
- </Window.Resources>
-
- <StackPanel>
- <ListBox ItemsSource="{StaticResource ds}"></ListBox>
- <ComboBox ItemsSource="{StaticResource ds}" Margin="5"></ComboBox>
- </StackPanel>
- </Window>
代碼中的DataTemplate的目標數據類型和ListBox的條目類型都是Unit:
- public class Unit
- {
- public string Year{get;set;}
- public int Price{get;set;}
- }
此時DataTemplate會自動加載到全部的Unit類型對象上,儘管我沒有爲ListBox和CompBox指定ItemTemplate,同樣會獲得下圖的效果:
不少時候數據是以XML形式存取的,若是把XML節點先轉換爲CLR數據類型再應用DataTemplate就麻煩了。DataTemplate很智能,具備直接把XML數據節點看成目標對象的功能-----XML數據中的元素名(標籤名)能夠做爲DataType,元素的子節點和Attribute可使用XPath來訪問。下面的代碼使用XmlDataProvider做爲數據源(其XPath指出的必須是一組節點),請注意細節之處的變化:
- <Window
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- x:Class="WPFApplication.Window4"
- x:Name="Window"
- Title="Window4"
- Width="314" Height="210">
- <Window.Resources>
-
- <DataTemplate DataType="Unit">
- <Grid>
- <StackPanel Orientation="Horizontal">
- <Grid>
- <Rectangle Stroke="Yellow" Fill="Orange" Width="{Binding XPath=@Price}"></Rectangle>
- <TextBlock Text="{Binding XPath=@Year}"></TextBlock>
- </Grid>
- <TextBlock Text="{Binding XPath=@Year}" Margin="5,0"></TextBlock>
- </StackPanel>
- </Grid>
- </DataTemplate>
-
- <XmlDataProvider x:Key="ds" XPath="Units/Unit">
- <x:XData>
- <Units xmlns="">
- <Unit Price="100" Year="2001"></Unit>
- <Unit Price="120" Year="2002"></Unit>
- <Unit Price="140" Year="2003"></Unit>
- <Unit Price="160" Year="2004"></Unit>
- <Unit Price="180" Year="2005"></Unit>
- <Unit Price="200" Year="2006"></Unit>
- </Units>
- </x:XData>
- </XmlDataProvider>
-
- </Window.Resources>
-
- <StackPanel>
- <ListBox ItemsSource="{Binding Source={StaticResource ds}}"></ListBox>
- <ComboBox ItemsSource="{Binding Source={StaticResource ds}}" Margin="5"></ComboBox>
- </StackPanel>
- </Window>
XML的優點就是能夠方便的表示帶有層級的數據,好比:年級----班級----小組 或 主菜單---次菜單----三級菜單。同時WPF準備了TreeView和MenuItem控件來顯示層級數據。可以幫助層級控件顯示層級數據的模板是HierachicalDataTemplate。下面兩個實際工做中常見的例子:
- <?xml version="1.0" encoding="utf-8" ?>
- <Data xmlns="">
- <Grade Name="一年級">
- <Class Name="甲班">
- <Group Name="A組">
- </Group>
- <Group Name="B組">
- </Group>
- <Group Name="C組">
- </Group>
- </Class>
- <Class Name="乙班">
- <Group Name="A組">
- </Group>
- <Group Name="B組">
- </Group>
- <Group Name="C組">
- </Group>
- </Class>
- </Grade>
- <Grade Name="二年級">
- <Class Name="甲班">
- <Group Name="A組">
- </Group>
- <Group Name="B組">
- </Group>
- <Group Name="C組">
- </Group>
- </Class>
- <Class Name="乙班">
- <Group Name="A組">
- </Group>
- <Group Name="B組">
- </Group>
- <Group Name="C組">
- </Group>
- </Class>
- </Grade>
- </Data>
程序XAML代碼以下:
- <Window x:Class="WPFApplication.Window6"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window6" Height="268" Width="362">
- <Window.Resources>
-
- <XmlDataProvider x:Key="ds" Source="XMLStudent.xml" XPath="Data/Grade"></XmlDataProvider>
-
- <HierarchicalDataTemplate DataType="Grade" ItemsSource="{Binding XPath=Class}">
- <TextBlock Text="{Binding XPath=@Name}"></TextBlock>
- </HierarchicalDataTemplate>
-
- <HierarchicalDataTemplate DataType="Class" ItemsSource="{Binding XPath=Group}">
- <RadioButton Content="{Binding XPath=@Name}" GroupName="gn"></RadioButton>
- </HierarchicalDataTemplate>
-
- <HierarchicalDataTemplate DataType="Group" ItemsSource="{Binding XPath=Student}">
- <CheckBox Content="{Binding XPath=@Name}"></CheckBox>
- </HierarchicalDataTemplate>
- </Window.Resources>
-
-
- <Grid>
- <TreeView Margin="5" ItemsSource="{Binding Source={StaticResource ds}}">
-
- </TreeView>
- </Grid>
- </Window>
程序運行效果以下圖:
第二個例子是同一種數據類型的嵌套結構,這種狀況下只設計一個HierarchicalDataTemplate就能夠了,它會產生自動迭代應用效果。
數據依然存放在XML文件中。數據所有是OPeration'類型:
- <?xml version="1.0" encoding="utf-8" ?>
- <Data xmlns="">
- <Operation Name="文件" Gesture="F">
- <Operation Name="新建" Gesture="N">
- <Operation Name="項目" Gesture="Ctr+P"/>
- <Operation Name="網站" Gesture="Ctr+W"/>
- <Operation Name="文檔" Gesture="Ctr+D"/>
- </Operation>
- <Operation Name="保存" Gesture="S"/>
- <Operation Name="打印" Gesture="P"/>
- <Operation Name="退出" Gesture="X"/>
- </Operation>
- <Operation Name="編輯" Gesture="E">
- <Operation Name="剪切" Gesture="Ctr+X"/>
- <Operation Name="複製" Gesture="Ctr+C"/>
- <Operation Name="粘貼" Gesture="Ctr+V"/>
- </Operation>
- </Data>
程序XAML代碼以下:
- <Window x:Class="WPFApplication.Window7"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window7" Height="300" Width="300">
- <Window.Resources>
-
- <XmlDataProvider x:Key="ds" Source="MenuXML.xml" XPath="Data/Operation"></XmlDataProvider>
-
- <HierarchicalDataTemplate DataType="Operation" ItemsSource="{Binding XPath=Operation}" >
- <StackPanel Orientation="Horizontal">
- <TextBlock Text="{Binding XPath=@Name}" Margin="10,0"></TextBlock>
- <TextBlock Text="{Binding XPath=@Gesture}"></TextBlock>
- </StackPanel>
- </HierarchicalDataTemplate>
- </Window.Resources>
- <StackPanel>
- <Menu ItemsSource="{Binding Source={StaticResource ds}}"></Menu>
- </StackPanel>
- </Window>
運行效果以下圖:
值得一提的是,HierarchicalDataTemplate的做用不是MenuItem的內容而是它的Header。若是對MenuItem的單擊事件進行偵聽處理,咱們就能夠從被單擊的MenuItem的Header中取出XML數據。
XAML代碼以下:
- <StackPanel MenuItem.Click="StackPanel_Click">
- <Menu ItemsSource="{Binding Source={StaticResource ds}}"></Menu>
- </StackPanel>
事件處理代碼以下:
- private void StackPanel_Click(object sender, RoutedEventArgs e)
- {
- MenuItem item = e.OriginalSource as MenuItem;
- XmlElement xe = item.Header as XmlElement;
- MessageBox.Show(xe.Attributes["Name"].Value);
- }
一旦拿到了數據,使用數據去驅動什麼樣的邏輯徹底由你來決定了。好比能夠維護一個CommandHelper類,根據拿到的數據來決定執行什麼RoutedCommand。
1.4.3 尋找失落的控件
「井水不犯河水」經常使用來形容兩個組織之間界限分明、互不相干,LogicTree與控件內部這顆小樹之間就保持着這種關係。換句話說,若是UI元素樹上有個X:Name=「TextBox1」的控件,某個控件內部也是由Template生成的x:Name="TextBox1"的控件,它們並不衝突,LogicTree不會看到控件內部的細節,控件內部元素也不會去理會控件外面是什麼值。你可能會想:「這樣一來,萬一我想從控件外部訪問內部的控件,獲取它的屬性值,豈不是作不到了。」放心,WPF爲咱們準備了訪問控件內部小世界的入口,如今咱們就開始出發尋找那些失落的控件。
由ControlTemplate和DataTemplate生成的控件都是「由Template生成的控件」。ControlTemplate和DataTemplate兩個類均派生自FrameWorkTemplate類,這個類有個名爲FindName的方法供咱們檢索其內部控件。也就是說,只要咱們能拿到Template,找到其內部控件就不成問題。對於ControlTemplate對象,訪問其目標控件的Datatemplate屬性就能夠拿到,但想拿到DataTemplate就要費一番周折了。千萬不要覺得ListBoxItem或者CompBoxItem容器就是DataTemplate的目標控件哦!由於控件的Template和ContentTemplate徹底是兩碼事。
咱們先來尋找由ControlTemplate生成的控件。首先設計一個ControlTemplate並把它應用在一個UserControl控件上。界面上還有一個Button,在它的Click事件處理器中咱們檢索ControlTemplate生成的代碼。
程序的XAML代碼以下:
- <Window x:Class="WPFApplication.Window8"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window8" Height="300" Width="300">
- <Window.Resources>
- <ControlTemplate x:Key="xtTemp">
- <StackPanel Background="Orange">
- <TextBox Margin="6" x:Name="textbox1"></TextBox>
- <TextBox Margin="6,0" x:Name="textbox2"></TextBox>
- <TextBox Margin="6" x:Name="textbox3"></TextBox>
- </StackPanel>
- </ControlTemplate>
- </Window.Resources>
- <StackPanel Background="Yellow">
- <UserControl x:Name="uc" Template="{StaticResource xtTemp}" Margin="5"></UserControl>
- <Button Content="Find By Name" Width="120" Height="30" Click="Button_Click"></Button>
- </StackPanel>
- </Window>
Button的事件處理器代碼以下:
- 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";
- (sp.Children[2] as TextBox).Text = "I Can Find YOU.";
- }
運行效果以下:
接下來咱們來尋找由DataTemplate生成的控件。不過在正式尋找以前,咱們先思考一個問題:尋找到一個由DataTemplate生成的控件以後,咱們想從中獲取哪些數據,若是想單純獲取與用戶界面相關的數據(好比控件的高度、寬度等),這麼作是正確的。可是若是是想獲取與業務邏輯相關的數據,那就要考慮是否是程序的設計出了問題------由於WPF採用的是數據驅動UI邏輯,獲取業務邏輯數據在底層就能作到,通常不會跑到表層來找。
先來看一個簡單的例子。做爲業務邏輯的類以下:
- public class Student38
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public string Skill { get; set; }
- public bool HasJob { get; set; }
- }
界面XAML代碼以下:
- <Window x:Class="WpfApplication1.Window38"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:local="clr-namespace:WpfApplication1"
- Title="Window38" Height="227" Width="269">
- <Window.Resources>
-
- <local:Student38 x:Key="stu" Id="1" Skill="WPF" Name="Timothy" HasJob="True"></local:Student38>
-
- <DataTemplate x:Key="dtStu">
- <Border BorderBrush="Orange" BorderThickness="2" CornerRadius="5">
- <StackPanel>
- <TextBlock Text="{Binding Id}" Margin="5"></TextBlock>
- <TextBlock x:Name="txtBlockName" Text="{Binding Name}" Margin="5"></TextBlock>
- <TextBlock Text="{Binding Skill}" Margin="5"></TextBlock>
- </StackPanel>
- </Border>
- </DataTemplate>
- </Window.Resources>
-
- <StackPanel>
- <ContentPresenter x:Name="cp" Content="{StaticResource stu}" ContentTemplate="{StaticResource dtStu}" Margin="5">
-
- </ContentPresenter>
- <Button Content="Find" Margin="5,0" Click="Button_Click">
-
- </Button>
- </StackPanel>
- </Window>
Button的事件處理器代碼以下:
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- TextBlock tb = this.cp.ContentTemplate.FindName("txtBlockName", this.cp) as TextBlock;
- MessageBox.Show(tb.Text);
-
-
-
- }
未被註釋的代碼是使用DataTemplate的FindName方法獲取由DataTemplate生成的控件並訪問其屬性,被註釋的代碼是直接使用其底層數據。顯然,爲了獲取Student的某個屬性,應該使用被註釋的代碼而沒必要要繞到控件上來,除非你想獲得的是控件的長度,高度。與業務邏輯無關的純UI屬性。
下面再看一個複雜的例子。DataTemplate最經常使用的地方就是GridViewColumn的CellTemplate屬性。把GridViewColumn放置在一個GridView控件裏就能夠生成表格了。GridViewColumn的默認CellTemplate是使用TextBlock只讀屬性顯示數據,若是咱們想讓用戶能修改數據或者使用CheckBox顯示bool類型的數據的話就須要自定義DataTemplate了。
仍是先定義這個Student的類:
- public class Student39
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public string Skill { get; set; }
- public bool HasJob { get; set; }
- }
準備數據集合,呈現數據的工做所有由XAML代碼來完成:
- <Window x:Class="WpfApplication1.Window39"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
- xmlns:local="clr-namespace:WpfApplication1"
- Title="Window39" Height="338" Width="446">
- <Window.Resources>
-
- <c:ArrayList x:Key="stuList">
- <local:Student39 Id="1" Name="Timoty Liu" Skill="WPF" HasJob="True"></local:Student39>
- <local:Student39 Id="2" Name="Tom Chang" Skill="BI/SQL" HasJob="True"></local:Student39>
- <local:Student39 Id="3" Name="Guan Chong" Skill="Writing" HasJob="False"></local:Student39>
- <local:Student39 Id="4" Name="Shanshan" Skill="C#/Java" HasJob="False"></local:Student39>
- <local:Student39 Id="5" Name="Pingping Zhang" Skill="Writing" HasJob="False"></local:Student39>
- <local:Student39 Id="6" Name="kenny Tian" Skill="Asp.net" HasJob="False"></local:Student39>
- </c:ArrayList>
-
- <DataTemplate x:Key="nameDT">
- <TextBox x:Name="txtBoxName" Text="{Binding Name}"></TextBox>
- </DataTemplate>
- <DataTemplate x:Key="skillDT">
- <TextBox x:Name="txtSkill" Text="{Binding Skill}"></TextBox>
- </DataTemplate>
- <DataTemplate x:Key="hasJobDT">
- <CheckBox IsChecked="{Binding HasJob}"></CheckBox>
- </DataTemplate>
- </Window.Resources>
- <Grid Margin="5">
- <ListView x:Name="lvStudent" ItemsSource="{StaticResource stuList}">
- <ListView.View>
- <GridView>
- <GridViewColumn Header="ID" DisplayMemberBinding="{Binding Id}"></GridViewColumn>
- <GridViewColumn Header="姓名" CellTemplate="{StaticResource nameDT}"></GridViewColumn>
- <GridViewColumn Header="技術" CellTemplate="{StaticResource skillDT}"></GridViewColumn>
- <GridViewColumn Header="已工做" CellTemplate="{StaticResource hasJobDT}"></GridViewColumn>
- </GridView>
- </ListView.View>
- </ListView>
- </Grid>
- </Window>
程序運行效果以下圖:
而後咱們爲顯示姓名的TextBox添加GetFocus事件處理器:
- <DataTemplate x:Key="nameDT">
- <TextBox x:Name="txtBoxName" Text="{Binding Name}" GotFocus="txtBoxName_GotFocus"></TextBox>
- </DataTemplate>
由於咱們是在DataTemplate裏面添加了事件處理器,因此界面上任何一個由此DataTemplate生成的TextBox都會在得到焦點的時候調用txtBoxName_GotFocus這個事件處理器。txtBoxName_GotFocus的代碼以下:
- private void txtBoxName_GotFocus(object sender, RoutedEventArgs e)
- {
- TextBox tb = e.OriginalSource as TextBox;
- ContentPresenter cp = tb.TemplatedParent as ContentPresenter;
- Student39 stu = cp.Content as Student39;
- this.lvStudent.SelectedItem = stu;
-
- ListViewItem lvi = this.lvStudent.ItemContainerGenerator.ContainerFromItem(stu) as ListViewItem;
- CheckBox cb = this.FindVisualChild<CheckBox>(lvi);
- MessageBox.Show(cb.Name);
-
- }
-
- private ChildType FindVisualChild<ChildType>(DependencyObject obj) where ChildType : DependencyObject
- {
- for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
- {
- DependencyObject child = VisualTreeHelper.GetChild(obj,i);
- if (child != null && child is ChildType)
- {
- return child as ChildType;
- }
- else
- {
- ChildType childOfChild = FindVisualChild<ChildType>(child);
- if(childOfChild!=null)
- {
- return childOfChild;
- }
- }
-
- }
- return null;
- }
當使用GridView做爲ListView的View屬性時,若是某一列使用TextBox做爲CellTemplate,那麼即便這列中的TextBox被鼠標單擊並得到了焦點ListView也不會把此項作爲本身的SelectedItem。因此,txtBoxName_GotFocus的前半部分是得到數據的源頭(TextBox),而後沿UI元素樹上朔到DataTemplate目標控件(ContentPresenter)並獲取它的內容,它的內容必定是一個Student實例。
txtBoxName_GotFocus的後半部分則藉助VisualTreeHelper類檢索由DataTemplate生成的控件。前面說過,每一個ItemControl的派生類(如ListBox,Combox,ListView)都具備本身獨特的條目容器,本例中是一個包裝着Student對象的ListViewItem(注意,此ListViewItem對象的Content也是Student對象)。能夠把這個ListViewItem控件視爲一顆樹的根,使用VisualTreeHelper類就能夠遍歷它的各個節點。本例中是吧遍歷算法分裝在了FindVisualChild泛型方法裏。
運行程序,並單擊某個顯示姓名的TextBox,效果以下圖所示:
由本例能夠看出,不管是從事件源頭「自下而上」的找,仍是使用ItemContainerGenerator.ContainerFromItem方法找到條目容器再「自上而下」的找,總之,找到業務邏輯數據(Student實例)並不難,而工做中大可能是操做業務邏輯數據。若是真的想找由DataTemplate生成的控件,對於結構簡單的控件,可使用DataTemplate對象的FindName方法;對於結構複雜的控件,則須要藉助VisualTreeHelper來實現。
1.5 深刻淺出話Style
Style直譯過來就是「風格」,「樣式」。拿人來舉例,人的風格指靜態的外觀和行爲舉止。一樣一我的,若是留平頭、穿上足球隊的隊服、腳蹬戰靴,看上去就是一名叱吒球場的運動員;若是讓他換一身筆挺的西裝、穿上皮靴、再拎上一個公文包,看上去就是一個上午人士;若是讓他梳起爆炸頭、戴上墨鏡、打上耳孔再穿一身肥大的休閒裝,活脫脫一個非主流形象。這些就是靜態的外觀風格,是經過改變一些屬性值的搭配起來實現的。除了從靜態外觀來判斷一我的的風格,咱們還會觀察他的行爲特色。好比遇到困難時,有些人很樂觀,照樣談笑風生。有些人很謹慎、仔細分析問題;有些人很悲觀、整天哀聲嘆氣,這就是行爲風格,行爲風格是由外界刺激的響應體現出來的。說到這兒,你們必定能想到一種職業---演員。演員就是靠調整本身的靜態行爲和風格來飾演各類角色的。
若是把WPF窗體看做一個舞臺,那麼窗體上的控件就是一個演員,它們的職責就是在用戶界面上按照業務邏輯的須要扮演本身的角色。爲了讓同一種控件能擔起不一樣的角色,程序員就要爲它們設計多種多樣的外觀樣式和行爲動做,這就是Style。構成Style最重要的兩種元素是Setter和Trigger,Setter類幫助咱們設置控件的靜態外觀風格,Trigger則幫助咱們設置控件的行爲風格。
1.5.1 Style中的Setter
Setter,設置器。什麼的設置器呢?屬性值的。咱們給屬性賦值的時候通常都採用「屬性名=屬性值」的形式。Setter類的Property屬性用來指明你想爲屬性的哪一個屬性賦值;Setter類的Value屬性則是你提供的屬性值。
下面的例子中在WIndows的資源詞典中放置一個針對TextBlock的Style,Style中使用若干Setter來設定TextBlock的一些屬性,這樣程序中的TextBlock就會具備統一的風格,除非你使用{x:null}顯示的清空Style。
XAML代碼以下:
- <Window x:Class="WpfApplication1.Window40"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window40" Height="310" Width="426">
- <Window.Resources>
- <Style TargetType="TextBlock">
- <Setter Property="FontSize" Value="24"></Setter>
- <Setter Property="TextDecorations" Value="Underline"></Setter>
- <Setter Property="FontStyle" Value="Italic"></Setter>
- </Style>
- </Window.Resources>
- <StackPanel Margin="5">
- <TextBlock Text="Hello WPF!"></TextBlock>
- <TextBlock Text="This is a sample for style!"></TextBlock>
- <TextBlock Text="by Time 2012-11-12!" Style="{x:Null}"></TextBlock>
- </StackPanel>
- </Window>
由於Style的內容屬性是Setters,因此咱們能夠直接在<Style>標籤的內容區域使用Setter。
運行效果以下圖:
根據上面這個例子咱們能夠推知,若是想設置控件的ControlTemplate,只須要把Setter的Property設爲Template併爲Value提供一個ControlTemplate對象便可。
1.5.2 Style的Trigger
Trigger,觸發器,即當某些條件知足的時候會觸發一個行爲(好比某些值的變化或動畫的發生等)。觸發器比較像事件。事件通常由用戶操做觸發的,而觸發器除了有事件觸發型的EventTrigger外還有數據變化觸發行的Trigger/DataTrigger及多條件觸發型MultiTrigger/MultiDataTrigger等。
1.基本的Trigger
Trigger是最基本的觸發器。相似於Setter,Trigger也有Property和Value兩個屬性,Property是Trigger關注的屬性名稱,Value是觸發條件。Trigger還有一個Setters屬性,此屬性是一組Setter,一旦觸發條件知足,這組Setter的「屬性---值」就會被應用,觸發條件不在知足後,各屬性值會被還原。
下面這個例子針對的是CheckBox的Style,當CheckBox的IsCheck屬性爲True時前景色和字體會改變。XAML代碼以下:
- <Window x:Class="WpfApplication1.Window41"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window41" Height="258" Width="352">
- <Window.Resources>
- <Style TargetType="CheckBox">
- <Style.Triggers>
- <Trigger Property="IsChecked" Value="True">
- <Trigger.Setters>
- <Setter Property="FontSize" Value="20"></Setter>
- <Setter Property="Foreground" Value="Orange"></Setter>
- </Trigger.Setters>
- </Trigger>
- </Style.Triggers>
- </Style>
- </Window.Resources>
- <Window.Background>
- <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
- <GradientStop Color="#FF4589D8" Offset="0" />
- <GradientStop Color="White" Offset="1" />
- </LinearGradientBrush>
- </Window.Background>
- <StackPanel>
- <CheckBox Content="鋤禾日當午" Margin="5"></CheckBox>
- <CheckBox Content="汗滴禾下土" Margin="5,0"></CheckBox>
- <CheckBox Content="誰知盤中餐" Margin="5"></CheckBox>
- <CheckBox Content="粒粒皆辛苦" Margin="5,0"></CheckBox>
- </StackPanel>
- </Window>
由於Triggers不是Style的內容屬性,因此<Style.Trigger>...</Style.Trigger>這層標籤不能省略,但Trigger的Setters屬性是Trigger的內容屬性,因此<Trigger.Setters>...</Trigger.Setters>這層標籤是能夠省略的。
運行效果以下圖:
2. MultiTrigger
MultiTrigger是一個很容易讓人誤解的名字,會讓人覺得是多個Trigger集成在一塊兒,實際上叫MultiConditionTrigger更合適,由於必須多個條件同時成立纔會被觸發。MultiTrigger比Trigger多了一個Conditions屬性,須要同時成立的條件就放在這個集合當中。
讓咱們稍微改動一下上面的例子,要求同時知足CheckBox被選中且Content必須爲「粒粒皆辛苦」時纔會被觸發,XAML代碼以下(僅Style部分):
- <Style TargetType="CheckBox">
- <Style.Triggers>
- <MultiTrigger>
- <MultiTrigger.Conditions>
- <Condition Property="IsChecked" Value="True"></Condition>
- <Condition Property="Content" Value="粒粒皆辛苦"></Condition>
- </MultiTrigger.Conditions>
- <MultiTrigger.Setters>
- <Setter Property="FontSize" Value="20"></Setter>
- <Setter Property="Foreground" Value="Orange"></Setter>
- </MultiTrigger.Setters>
- </MultiTrigger>
- </Style.Triggers>
- </Style>
運行效果以下圖:
3. 由數據觸發DataTrigger
程序中常常會遇到基於數據執行某些判斷狀況,遇到這種狀況咱們就能夠考慮使用DataTrigger。DataTrigger對象的Binding屬性會把數據源源不斷的送出來,一旦送出來的值與Value屬性一致,DataTrigger即被觸發。
下面的例子中,當TextBox的Text長度小於7個字符其Border會保持紅色。XAML代碼以下:
- <Window x:Class="WpfApplication1.Window42"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:local="clr-namespace:WpfApplication1"
- Title="Window42" Height="184" Width="324">
- <Window.Resources>
- <local:L2BConverter x:Key="cbtr"></local:L2BConverter>
- <Style TargetType="TextBox">
- <Style.Triggers>
- <DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self},Path=Text.Length,Converter={StaticResource cbtr}}" Value="false">
- <Setter Property="BorderBrush" Value="Red"></Setter>
- <Setter Property="BorderThickness" Value="1"></Setter>
- </DataTrigger>
-
- </Style.Triggers>
- </Style>
- </Window.Resources>
- <StackPanel>
- <TextBox Margin="5"></TextBox>
- <TextBox Margin="5,0"></TextBox>
- <TextBox Margin="5"></TextBox>
- </StackPanel>
- </Window>
這個例子中惟一須要解釋的就是DataTrigger的Binding。爲了將控件自身作爲數據源,咱們使用了RelativeSource,初學者常常認爲「不明確指出Source的值Binding就會將本身做爲數據的來源」,這是錯誤的,由於不明確指出Source的值Binding就會把控件的DataContext作爲本身的數據來源。Binding的Path設置爲Text.Length,即咱們關注的是字符串的長度。長度是一個具體的數字,如何基於這個長度值來作判斷呢?這就用到了Converter。咱們建立以下Converter:
- public class L2BConverter : IValueConverter
- {
-
- public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
- {
- int textLength = (int)value;
- return textLength > 6 ? true : false;
- }
-
- public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
- {
- throw new NotImplementedException();
- }
- }
通過Converter轉換之後,長度值就會變爲bool類型值。DataTrigger的value設置爲false,也就是說當TextBox的文本長度小於7時DataTrigger會使用本身一組Setter把TextBox的邊框設置爲紅色。運行效果以下圖:
4. 多數據條件觸發的MultiDataTrigger
有時候咱們會遇到要求多個數據條件同時知足才能觸發變化的需求,此時能夠考慮使用MultiDataTrigger。好比有這樣一個需求:用戶界面上使用ListBox顯示一列Student數據,當Student對象知足ID爲二、Name爲Darren的時候,條目就高亮顯示。
事例XAML代碼以下:
- <Window x:Class="WpfApplication1.Window43"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window43" Height="262" Width="425">
- <Window.Resources>
- <Style TargetType="ListBoxItem">
-
- <Setter Property="ContentTemplate">
- <Setter.Value>
- <DataTemplate>
- <StackPanel Orientation="Horizontal">
- <TextBlock Text="{Binding Id}" Width="60"></TextBlock>
- <TextBlock Text="{Binding Name}" Width="120"></TextBlock>
- <TextBlock Text="{Binding Skill}" Width="60"></TextBlock>
- </StackPanel>
- </DataTemplate>
- </Setter.Value>
- </Setter>
-
- <Style.Triggers>
- <MultiDataTrigger>
- <MultiDataTrigger.Conditions>
- <Condition Binding="{Binding Path=Id}" Value="2"></Condition>
- <Condition Binding="{Binding Path=Name}" Value="Darren"></Condition>
- </MultiDataTrigger.Conditions>
- <MultiDataTrigger.Setters>
- <Setter Property="Background" Value="Orange"></Setter>
- </MultiDataTrigger.Setters>
- </MultiDataTrigger>
- </Style.Triggers>
- </Style>
- </Window.Resources>
- <StackPanel>
- <ListBox x:Name="lbInfos" Margin="5"></ListBox>
- </StackPanel>
- </Window>
後臺代碼以下:
- public Window43()
- {
- InitializeComponent();
- InitialInfo();
- }
-
- private void InitialInfo()
- {
- List<Student38> infos = new List<Student38>() {
- new Student38(){ Id=2, Name="Darren", Skill="WPF"},
- new Student38(){ Id=1, Name="Tom", Skill="Java"},
- new Student38(){ Id=3, Name="Jacky", Skill="Asp.net"},
- new Student38(){ Id=2, Name="Andy", Skill="C#"},
- };
- this.lbInfos.ItemsSource = infos;
- }
Student38類已經在上面的文章中提到,再此就再也不多講。運行效果以下圖:
5. 由事件觸發的EventTrigger
EventTrigger是觸發器中最特殊的一個。首先,它不是由屬性值或者數據的變化來觸發而是由事件來觸發;其次,被觸發之後它並不是應用一組Setter,而是執行一段動畫。所以,UI的動畫效果每每和EventTrigger相關聯。
在下面這個例子中建立一個針對Button的Style,這個Style包含兩個EventTrigger,一個由MouseEnter觸發,另外一個由MouseLeave觸發。XAML代碼以下:
- <Window x:Class="WpfApplication1.Window44"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="Window44" Height="258" Width="377">
- <Window.Resources>
- <Style TargetType="Button">
- <Style.Triggers>
-
- <EventTrigger RoutedEvent="MouseEnter">
- <BeginStoryboard>
- <Storyboard>
- <DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Width"></DoubleAnimation>
- <DoubleAnimation To="150" Duration="0:0:0.2" Storyboard.TargetProperty="Height"></DoubleAnimation>
- </Storyboard>
- </BeginStoryboard>
- </EventTrigger>
-
- <EventTrigger RoutedEvent="MouseLeave">
- <BeginStoryboard>
- <Storyboard>
- <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Width"></DoubleAnimation>
- <DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="Height"></DoubleAnimation>
- </Storyboard>
- </BeginStoryboard>
- </EventTrigger>
- </Style.Triggers>
- </Style>
- </Window.Resources>
- <Grid>
- <Button Width="40" Height="40" Content="OK"></Button>
- </Grid>
- </Window>
無需任何c#代碼,咱們就得到了以下圖所示的結果:
自此,各類觸發器就介紹完了,提醒你們一點:雖然在Style裏面大量使用觸發器,但觸發器並不是只能應用在Style中-----各類Template也能夠擁有本身的觸發器,請你們根據須要決定觸發器放在Style裏面仍是Template裏面。