[WPF]WPF中如何实现数据与表示分离。(二) —— Binding(下)
2006-01-20 09:32 Colin Han 阅读(4622) 评论(8) 编辑 收藏 举报“我怎么知道什么时候数据改变?”
“我可能必须利用反射去访问数据,而反射的性能会很低。”
“{Binding Path=Red}是什么东西?”
好在这一切都只是我的一个想象,微软已经为我们提供了解决方案。这里就要引入WPF中的DependencyProperty的概念了。
在我刚刚开始接触到WPF的DependencyProperty的时候,仅仅觉得是一个很巧妙的实现,随着学习WPF的深入,也来越觉得DependencyProperty其实是WPF的核心技术点之一。
DependencyProperty和DependencyObject配合,提供了WPF中基本的数据存储、访问和通知的机制。也正是因为这两个东西的存在,使得XAML,Binding,Animation都成为可能。
让我们来看一下重新实现后的数据层。
2using System.Windows;
3using System.Windows.Media;
4
5namespace ColorPicker2
6{
7 public class ColorPickerData : DependencyObject
8 {
9 public ColorPickerData()
10 {
11 }
12 public static DependencyProperty BackgroundProperty = DependencyProperty.Register(
13 "Background",
14 typeof(Brush),
15 typeof(ColorPickerData),
16 new PropertyMetadata(new SolidColorBrush(Colors.Black))
17 );
18
19 public static DependencyProperty RedProperty = DependencyProperty.Register(
20 "Red",
21 typeof(byte),
22 typeof(ColorPickerData)
23 );
24
25 public static DependencyProperty GreenProperty = DependencyProperty.Register(
26 "Green",
27 typeof(byte),
28 typeof(ColorPickerData)
29 );
30
31 public static DependencyProperty BlueProperty = DependencyProperty.Register(
32 "Blue",
33 typeof(byte),
34 typeof(ColorPickerData)
35 );
36
37
38 public Brush Background
39 {
40 get { return (Brush)GetValue(BackgroundProperty); }
41 set { SetValue(BackgroundProperty, value); }
42 }
43
44 public byte Red
45 {
46 get { return (byte)GetValue(RedProperty); }
47 set { SetValue(RedProperty, value); }
48 }
49
50 public byte Green
51 {
52 get { return (byte)GetValue(GreenProperty); }
53 set { SetValue(GreenProperty, value); }
54 }
55
56 public byte Blue
57 {
58 get { return (byte)GetValue(BlueProperty); }
59 set { SetValue(BlueProperty, value); }
60 }
61
62 protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
63 {
64 if (e.Property.Equals(RedProperty) ||
65 e.Property.Equals(GreenProperty) ||
66 e.Property.Equals(BlueProperty))
67 {
68 this.Background = new SolidColorBrush(Color.FromRgb(this.Red, this.Green, this.Blue));
69 }
70
71 base.OnPropertyChanged(e);
72 }
73 }
74}
75
在行12-35,我们注册了4个DependencyProperty:RedProperty, GreenProperty, BlueProperty 和 BackgroundProperty。我们也不再使用成员字段去保存这些属性的具体值。而是直接使用DependencyObject上的GetValue和SetValue方法设置和读取值。
当Red, Green, Blue属性发生变化的时候,我们来同步Background属性(注1)。同时,DependencyObject也会使用特定的方式向关联的对象(例如:Binding)发出通知。
Ok,通知的问题解决了。我们开发人员以后不再需要去制定或维护这套数据层和表示层通讯的策略。然后,我们就需要解决第二个问题:使用反射获得对象的属性的性能问题。
从前面的Code中,也许细心的你已经发现,我们不再需要使用反射去获得对象的某个属性了。因为DependencyObject已经提供了GetValue和SetValue方法。数据绑定管理者(暂且先这样叫吧)可以通过这两个方法容易的获得或设置任何一个属性的值(注2)。而且,前面的属性注册的机制,也让微软有一个机会来优化内部的数据存储,提高性能(注3)。剩下的性能问题可能就出在了Boxing和UnBoxing上了,毕竟很多属性都是值类型的。但是,以目前的结构,微软在JIT上对这一部分进行一些专门的优化也并非不可能。个人猜想,具体情况如何,留待各位去研究了。
啊哈,剩下最后一个问题了。{Binding Path=Red}是什么?微软的官方称呼为:MarkupExtension。或者可以翻译为标记扩展。我写过一篇Blog简单的说了如何自定义一个MarkupExtension,虽然当时我没有调通,但是我认为已经概括性的告诉了你,MarkupExtension是如何工作的。
上面的这条语句也就是说,Slider的Value属性的值是由Binding这个MarkupExtension对象提供(ProvideValue)的。
Ok,Clear。但是,这样就够了吗?所有的问题都解决了吗?这样的设计已经是最好的了吗?
“不!”(也许各位的客户也经常这样向你喊。)
“Brush是个什么东西?我的数据模块只关心Color,不关心Brush!”
“Background的更新逻辑太土了。”(就象很多你们碰到的用户一样,用户有时候就希望能有一个非常漂亮的界面。)
没问题,我将在后面的文章中讨论如何改进这些问题,让我们的数据模块和表示层尽量的分离。
最后,附上本节的源代码:ColorPicker2.rar
注意:本文相关的例子都在WinFX 12月CTP下测试通过,因为目前环境限制,没能再今年1月CTP下进行测试,请见谅。
注1:其实,这一部分,微软已经为我们提供了更巧妙的途径,留待下一节来讲解吧。
注2:其实,不光数据绑定,XAML其实也是使用这两个方法来访问对象的属性的
注3:反编译代码后,证实了我的这个设想,微软为DependencyProperty实现了一套高效的Hash值算法来优化存储。