步遥情感网
您的当前位置:首页WPF模板(一)详细介绍

WPF模板(一)详细介绍

来源:步遥情感网
WPF模板(⼀)详细介绍

本次随笔来源于电⼦书,⼈家的讲解很好,我就不画蛇添⾜了。

图形⽤户界⾯应⽤程序较之控制台界⾯应⽤程序最⼤的好处就是界⾯友好、数据显⽰直观。CUI程序中数据只能以⽂本的形式线性显⽰,GUI程序则允许数据以⽂本、列表、图形等多种形式⽴体显⽰。

⽤户体验在GUI程序设计中起着举⾜轻重的作⽤-----⽤户界⾯设计成什么样看上去才⾜够的漂亮?控件如何安排才简单易⽤并且少犯错误?这些都是设计师需要考虑的问题。WPF系统不但⽀持传统的Winfrom编程的⽤户界⾯和⽤户体验设计,更⽀持使⽤专门的设计⼯具Blend进⾏专业设计,同时还推出了以模板为核⼼的新⼀代设计理念。1.1 模板的内涵

从字⾯上看,模板就是“具有⼀定规格的样板”,有了它,就可以依照它制造很多⼀样是实例。我们常把看起来⼀样的东西称为“⼀个模⼦⾥⾯刻出来的。”就是这个道理。然⽽,WPF中的模板的内涵远⽐这个深刻。

Binding和基于Binding数据驱动UI是WPF的核⼼部分,WPF最精彩的部分是什么呢?依我看,既不是美轮美奂的3D图形,也不是炫⽬多彩的动画,⽽是默默⽆闻的模板(Template)。实际上,就连2D/3D绘图也常常是为它锦上添花。

Templdate究竟有什么能⼒能够使得它在WPF体系中获此殊荣呢?这还要从哲学谈起,“形⽽上者谓之道,形⽽下者谓之器”,这句话出⾃《易经》,⼤意是我们能够观察到的世间万物形象之上的抽象的结果就是思维,⽽形象之下掩盖的就是其本质。显然,古⼈已经注意到“形”是连接本质和思维的枢纽,让我们把这句话引⼊计算机世界。

“形⽽上者谓之道”指的就是基于现实世界对万物进⾏抽象封装,理顺它们之间的关系,这个“道”不就是⾯向对象思想吗!如果再把⾯向对象进⼀步提升、总结出最优的对象组合关系,“道”就上升为设计模式思想。

“形⽽下者谓之⽓”指的是我们能够观察到的世间万物都是物质类容的本质表现形式。“本质与表现”或者说“类容与形式”是哲学范畴内的⼀对⽭盾体。

软件之道并⾮本书研究的主要类容,本书研究的是WPF。WPF全称Windows Presentation Foundation,Presentation⼀词的意思就是外观,呈现,表现,也就是说,在WIndows GUI程序这个尺度上,WPF扮演的就是“形”的⾓⾊、是程序的外在形式,⽽程序的内容仍然是由数据和算法构成的业务逻辑。与WPF类似,Winform和Asp.net也都是内容的表现形式。

让我们把尺度缩⼩到WPF内部。这个系统与程序内容(业务逻辑)的边界是Binding,Binding把数据源源不断从程序内部送出来交由界⾯元素来显⽰,⼜把从界⾯元素搜集到的数据传回程序内部。界⾯元素间的沟通则依靠路由事件来完成。有时候路由事件和附加事件也会参与到数据的传输中。让我们思考⼀个问题:WPF作为Windows的表⽰⽅式,它究竟表⽰的是什么?换句话说,WPF作为⼀种“形式”,它表现的内容到底是什么?答案是程序的数据和算法----Binding传递的是数据,事件参数携带的也是数据;⽅法和委托的调⽤是算法,事件传递消息也是算法----数据在内存⾥就是⼀串串字符或字符。算法是⼀组组看不见摸不着的抽象逻辑,如何恰如其分的把它们展现给⽤户呢?

加⼊想表达⼀个bool类型,同时还想表达⽤户可以在这两个值之间⾃由切换这样⼀个算法,你会怎么做?你⼀定会想使⽤⼀个CheckBox控件来满⾜要求;再⽐如颜⾊值实际上是⼀串数字,⽤户基本上不可能只看数字就能想象出真正的颜⾊,⽽且⽤户也不希望只靠输⼊字符来表⽰颜⾊值,这时,颜⾊值这⼀“数据内容”的恰当表现形式就是⼀个填充着真实颜⾊的⾊块。,⽽⽤户即可以输⼊值⼜可以⽤取⾊吸管取⾊来设置值的“算法内容”恰当的表达⽅式是创建⼀个

ColorPicker控件。相信你已经发现,控件(Control)是数据内容表现形式的双重载体。换句话说,控件即是数据的表现形式让⽤户可以直观的看到数据,⼜是算法的表现形式让⽤户⽅便的操作逻辑。

作为表现形式,每个控件都是为了实现某种⽤户操作算法和直观显⽰某种数据⽽⽣,⼀个控件看上去是什么样⼦由它的“算法内容”和“数据内容决定”,这就是内容决定形式,这⾥,我们引⼊两个概念:

控件的算法内容:值控件能展⽰哪些数据、具有哪些⽅法、能相应哪些操作、能激发什么事件,简⽽⾔之就是控件的功能,它们是⼀组相关的算法逻辑。控件的数据内容:控件具体展⽰的数据是什么。

以往的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代码发给他们。其代码如下:1. 2.

3. 4.

5. 6. 7.

8. 9. 10.

11.

我想,尽管你还没有学习什么DataTempldate,但借助前⾯学习的基础⼀样可以看个⼋九不离⼗了。1.2 数据的外⾐DataTemplate

“横看成岭侧成峰,远近⾼低各不同”庐⼭的美景如此,数据⼜何尝不是这样呢?同样⼀条数据,⽐如具有ID、Name、PhoneNumber、Address等Student的实例,放在GridView⾥⾯有时可能是简单的⽂本、每个单元格只显⽰⼀个属性;放在ListBox⾥⾯有时为了避免单调可以在最左端显⽰⼀个*的⼩图像,再将其它信息分两⾏显⽰在其后⾯;如果单独显⽰⼀个学⽣信息则可以⽤类似简历的复杂格式来展现学⽣的全部数据。⼀样的内容可以⽤不同的形式来展现,软件设计称之为“数据--视图”模式。以往的开发技术,如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数据类型:

1. public class Car 2. {

3. public string AutoMark { get; set; } 4. public string Name { get; set; } 5. public string Year { get; set; }

6. public string TopSpeed { get; set; } 7. }

为了在ListBox⾥⾯显⽰Car类型的数据,我们需要准备⼀个UserControl。命名为CarListItemView。

这个UserControl由⼀个Car类型实例在背后⽀持,当设置这个实例的时候,界⾯元素将实例的属性值显⽰在各个控件⾥。CarListItemView的XAML代码如下:

1. 2. xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" 3. xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"> 4.

5.

6. 7.

8. 9. 10. 11. 12.

13.

CarlistItemView⽤于⽀持前台显⽰属性C#代码为:1. ///

2. /// CarListViewItem.xaml 的交互逻辑 3. ///

4. public partial class CarListViewItem : UserControl 5. {

6. public CarListViewItem() 7. {

8. InitializeComponent(); 9. } 10.

11. private Car car; 12.

13. public Car Car 14. {

15. get { return car; } 16. set 17. {

18. car = value;

19. this.txtBlockName.Text = car.Name; 20. this.txtBlockYear.Text = car.Year;

21. this.igLogo.Source = new BitmapImage(new Uri(@\"Resource/Image/\"+car.AutoMark+\".png\22. } 23. } 24. }

类似的原理,我们需要为Car类型准备⼀个详细信息视图。UserControl名称为CarDetailView,XAML部分代码如下:

1. 2. xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" 3. xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">

4. 5.

6. 7.

8. 9. 10.

11.

12. 13. 14. 15.

16.

17. 18.

19.

20. 21.

22.

23. 24.

25. 26. 27. 28. 29. 后台⽀持数据⼤同⼩异:

1. ///

2. /// CarDetailView.xaml 的交互逻辑 3. ///

4. public partial class CarDetailView : UserControl 5. {

6. public CarDetailView() 7. {

8. InitializeComponent(); 9. } 10.

11. private Car car; 12.

13. public Car Car 14. {

15. get { return car; } 16. set 17. {

18. car = value;

19. this.txtBlockName.Text = car.Name;

20. this.txtBlockAutoMark.Text = car.AutoMark; 21. this.txtBlockYear.Text = car.Year;

22. this.txtTopSpeed.Text = car.TopSpeed;

23. this.imgPhoto.Source = new BitmapImage(new Uri(@\"Resource/Image/\" + car.Name + \".jpg\24. } 25. } 26. }

最后把它们组装到窗体上:

1. 2. xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" 3. xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"

4. Title=\"Window35\" Height=\"350\" Width=\"623\" xmlns:my=\"clr-namespace:WpfApplication1\"> 5. 6.

7.

8.

9. 10. 11. 窗体的后台代码如下:

1. using System;

2. using System.Collections.Generic; 3. using System.Linq; 4. using System.Text;

5. using System.Windows;

6. using System.Windows.Controls; 7. using System.Windows.Data;

8. using System.Windows.Documents; 9. using System.Windows.Input; 10. using System.Windows.Media;

11. using System.Windows.Media.Imaging; 12. using System.Windows.Shapes; 13.

14. namespace WpfApplication1 15. {

16. ///

17. /// Window35.xaml 的交互逻辑 18. ///

19. public partial class Window35 : Window 20. {

21. public Window35() 22. {

23. InitializeComponent(); 24. InitialCarList(); 25. } 26.

27. private void listBoxCars_SelectionChanged(object sender, SelectionChangedEventArgs e) 28. {

29. CarListViewItem viewItem = e.AddedItems[0] as CarListViewItem; 30. if(viewItem!=null) 31. {

32. carDetailView1.Car = viewItem.Car; 33. } 34. } 35.

36. private void InitialCarList() 37. {

38. List infos = new List() {

39. new Car(){ AutoMark=\"Aodi\40. new Car(){ AutoMark=\"Aodi\41. new Car(){ AutoMark=\"Aodi\42. new Car(){ AutoMark=\"Aodi\43. new Car(){ AutoMark=\"Aodi\44. };

45. foreach (Car item in infos) 46. {

47. CarListViewItem viewItem = new CarListViewItem(); 48. viewItem.Car = item;

49. this.listBoxCars.Items.Add(viewItem); 50. } 51. } 52. } 53.

54. public class Car 55. {

56. public string AutoMark { get; set; } 57. public string Name { get; set; } 58. public string Year { get; set; }

59. public string TopSpeed { get; set; } 60. } 61. }

运⾏并单击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代码如下:

1. //⼚商名称转换为Logo路径

2. public class AutoMarkToLogoPathConverter:IValueConverter 3. {

4. ///

5. /// 正向转

6. ///

7. ///

8. /// 9. /// 10. /// 11. ///

12. public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 13. {

14. return new BitmapImage(new Uri(string.Format(@\"Resource/Image/{0}.png\15. }

16. ///

17. /// 逆向转未⽤到 18. ///

19. ///

20. /// 21. /// 22. /// 23. ///

24. public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 25. {

26. throw new NotImplementedException(); 27. } 28. }

1. //汽车名称转换为照⽚路径

2. public class NameToPhotoPathConverter:IValueConverter 3. {

4. ///

5. /// 正向转

6. ///

7. ///

8. /// 9. /// 10. /// 11. ///

12. public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 13. {

14. return new BitmapImage(new Uri(string.Format(@\"Resource/Image/{0}.jpg\15. }

16. ///

17. /// 逆向转未⽤到 18. ///

19. ///

20. /// 21. /// 22. /// 23. ///

24. public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 25. {

26. throw new NotImplementedException(); 27. } 28. }

有了这两个Converter之后我们就可以设计DataTemplate了,完整的XAML代码如下:

1. 2. xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" 3. xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" 4. xmlns:local=\"clr-namespace:WpfApplication1.Model\" 5. Title=\"Window36\" Height=\"350\" Width=\"623\"> 6. 7.

8. 9. 10.

11.

12. 13.

14. 15.

16. 17. 18.

19.

20. 21. 22. 23.

24.

25. 26.

27.

28. 29.

30.

31. 32.

33. 34. 35. 36.

37.

38. 39. 40.

41.

42. {StaticResource amp}}\">

43.

44. 45. 46. 47. 48.

49. 50. 51.

52.

53. {Binding Path=SelectedItem,ElementName=lbInfos}\">

54. 55. 56.

代码对于初学者来说有点长但是结构⾮常简单。其中最重要的有两句:

ContentTemplate=\"{StaticResource DatialViewTemplate}\",相当于给⼀个普通的UserControl穿上了⼀件外⾐、让Car数据以图⽂并茂的⽅式展现出来。这件外⾐就是x:Key=\"DatialViewTemplate\"标记的DataTemplate资源。

ItemTemplate=\"{StaticResource ItemView}\",把每⼀件数据的外⾐交给ListBox,当ListBox的ItemSource被赋值的时候,ListBox就会为每个条⽬穿上这件外⾐。这件外⾐是以x:Key=\"ItemView\"标记的DataTemplate资源。

因为不再使⽤事件驱动,⽽且为数据穿⾐服的事也已经⾃动完成,所以后台的C#代码就⾮常的简单。窗体的C#代码就只剩下这些:

1. ///

2. /// Window36.xaml 的交互逻辑 3. ///

4. public partial class Window36 : Window 5. {

6. public Window36() 7. {

8. InitializeComponent(); 9. InitialCarList(); 10. } 11.

12. private void InitialCarList() 13. {

14. List infos = new List() {

15. new Car(){ AutoMark=\"Aodi\16. new Car(){ AutoMark=\"Aodi\17. new Car(){ AutoMark=\"Aodi\18. new Car(){ AutoMark=\"Aodi\19. new Car(){ AutoMark=\"Aodi\20. };

21. this.lbInfos.ItemsSource = infos; 22. } 23. }

运⾏程序,效果如下图:

与之前⽤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不具备的属性值、设置它的圆⾓弧度即可。

更改后的核⼼代码如下:[html]

1. 这段代码有以下⼏个看点:

看点⼀:作为资源的不是单纯的ControlTemplate⽽是Style,说是编辑ControlTemplate但是实际上是吧ControlTemplate包含在Style⾥,不知道微软会不会更正这个⼩⿇烦。Style是什么呢?简单讲就是⼀组,也就是⼀组属性设计器。回想⼀下Winfrom编程的时候,窗体设计器不是可以⽣成这样的代码吗:

1. //

2. // button1 3. //

4. this.button1.Location = new System.Drawing.Point(1100, 199); 5. this.button1.Name = \"button1\";

6. this.button1.Size = new System.Drawing.Size(75, 23); 7. this.button1.TabIndex = 0; 8. this.button1.Text = \"报表\";

9. this.button1.UseVisualStyleBackColor = true;

10. this.button1.Click += new System.EventHandler(this.button1_Click); 11. //

12. // printPreviewDialog1 13. //

14. this.printPreviewDialog1.AutoScrollMargin = new System.Drawing.Size(0, 0); 15. this.printPreviewDialog1.AutoScrollMinSize = new System.Drawing.Size(0, 0); 16. this.printPreviewDialog1.ClientSize = new System.Drawing.Size(400, 300); 17. this.printPreviewDialog1.Enabled = true;

18. this.printPreviewDialog1.Icon = ((System.Drawing.Icon)(resources.GetObject(\"printPreviewDialog1.Icon\"))); 19. this.printPreviewDialog1.Name = \"printPreviewDialog1\"; 20. this.printPreviewDialog1.Visible = false; 同样的逻辑如果在XAML代码⾥出就变成了这样:

1.

11. 12.

13.

14.

15. 16. 17.

因为Style的内容属性是Setters,所以我们可以直接在

16. 17.

18. 19. 20. 21. 22. 23.

24. 25. 26. 27. 28. 29.

因为Triggers不是Style的内容属性,所以...这层标签不能省略,但Trigger的Setters属性是Trigger的内容属性,所以...这层标签是可以省略的。运⾏效果如下图:2. MultiTrigger

MultiTrigger是⼀个很容易让⼈误解的名字,会让⼈以为是多个Trigger集成在⼀起,实际上叫MultiConditionTrigger更合适,因为必须多个条件同时成⽴才会被触发。MultiTrigger⽐Trigger多了⼀个Conditions属性,需要同时成⽴的条件就放在这个集合当中。

让我们稍微改动⼀下上⾯的例⼦,要求同时满⾜CheckBox被选中且Content必须为“粒粒皆⾟苦”时才会被触发,XAML代码如下(仅Style部分):

1.

运⾏效果如下图:

3. 由数据触发DataTrigger

程序中经常会遇到基于数据执⾏某些判断情况,遇到这种情况我们就可以考虑使⽤DataTrigger。DataTrigger对象的Binding属性会把数据源源不断的送出来,⼀旦送出来的值与Value属性⼀致,DataTrigger即被触发。

下⾯的例⼦中,当TextBox的Text长度⼩于7个字符其Border会保持红⾊。XAML代码如下:

1. 2. xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" 3. xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" 4. xmlns:local=\"clr-namespace:WpfApplication1\" 5. Title=\"Window42\" Height=\"184\" Width=\"324\"> 6.

7. 8.

17. 18.

19. 20. 21. 22. 23.

这个例⼦中唯⼀需要解释的就是DataTrigger的Binding。为了将控件⾃⾝做为数据源,我们使⽤了RelativeSource,初学者经常认为“不明确指出Source的值Binding就会将⾃⼰作为数据的来源”,这是错误的,因为不明确指出Source的值Binding就会把控件的DataContext做为⾃⼰的数据来源。Binding的Path设置为Text.Length,即我们关注的是字符串的长度。长度是⼀个具体的数字,如何基于这个长度值来做判断呢?这就⽤到了Converter。我们创建如下Converter:

1. public class L2BConverter : IValueConverter 2. { 3.

4. public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 5. {

6. int textLength = (int)value;

7. return textLength > 6 ? true : false; 8. } 9.

10. public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 11. {

12. throw new NotImplementedException(); 13. } 14. }

经过Converter转换以后,长度值就会变为bool类型值。DataTrigger的value设置为false,也就是说当TextBox的⽂本长度⼩于7时DataTrigger会使⽤⾃⼰⼀组Setter把TextBox的边框设置为红⾊。运⾏效果如下图:4. 多数据条件触发的MultiDataTrigger

有时候我们会遇到要求多个数据条件同时满⾜才能触发变化的需求,此时可以考虑使⽤MultiDataTrigger。⽐如有这样⼀个需求:⽤户界⾯上使⽤ListBox显⽰⼀列Student数据,当Student对象满⾜ID为2、Name为Darren的时候,条⽬就⾼亮显⽰。事例XAML代码如下:

1. 2. xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" 3. xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" 4. Title=\"Window43\" Height=\"262\" Width=\"425\"> 5.

6.

32. 33.

34. 35. 36. 后台代码如下:

1. public Window43() 2. {

3. InitializeComponent(); 4. InitialInfo(); 5. } 6.

7. private void InitialInfo() 8. {

9. List infos = new List() {

10. new Student38(){ Id=2, Name=\"Darren\11. new Student38(){ Id=1, Name=\"Tom\12. new Student38(){ Id=3, Name=\"Jacky\13. new Student38(){ Id=2, Name=\"Andy\14. };

15. this.lbInfos.ItemsSource = infos; 16. }

Student38类已经在上⾯的⽂章中提到,再此就不再多讲。运⾏效果如下图:5. 由事件触发的EventTrigger

EventTrigger是触发器中最特殊的⼀个。⾸先,它不是由属性值或者数据的变化来触发⽽是由事件来触发;其次,被触发以后它并⾮应⽤⼀组Setter,⽽是执⾏⼀段动画。因此,UI的动画效果往往和EventTrigger相关联。

在下⾯这个例⼦中创建⼀个针对Button的Style,这个Style包含两个EventTrigger,⼀个由MouseEnter触发,另⼀个由MouseLeave触发。XAML代码如下:

1. 2. xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" 3. xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" 4. Title=\"Window44\" Height=\"258\" Width=\"377\"> 5.

6.

28. 29.

30. 31. 32.

⽆需任何c#代码,我们就获得了如下图所⽰的结果:

⾃此,各种触发器就介绍完了,提醒⼤家⼀点:虽然在Style⾥⾯⼤量使⽤触发器,但触发器并⾮只能应⽤在Style中-----各种Template也可以拥有⾃⼰的触发器,请⼤家根据需要决定触发器放在Style⾥⾯还是Template⾥⾯。

因篇幅问题不能全部显示,请点此查看更多更全内容