WPF 中最大的亮点当属 Binding 了,.NET 给我们提供了足够多的属性来让我们非常方便地进行数据绑定等操作。但即使如此,依然会有想编写自己的属性来进行 Binding 等操作。
在 WPF 中,微软将属性的概念延伸了一下,推出了“依赖属性”这个概念。所谓依赖属性,就是自己本身没有值而值是依赖别人的值。
为什么要使用依赖属性呢?举个例子,一个 TextBlock 有上百个属性,但是基本上我们常用的就是 Text 属性,当你在使用构造函数创建这个 TextBlock 的时候,这个实例的控件就已经被定下来了,随之而来的是大量不必要的空间浪费。而依赖属性,依赖别人的值而设定自己的值,这节省了大量不必要的空间浪费,而且可以方便的进行数据绑定,所以我们才要用依赖属性。
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content",typeof(string),typeof(Test));
首先依赖属性的访问权限可以是 public 或者 private 和 protected,但是如果是 private 或者 protected 的话,要想让外界访问,就需要给这个依赖属性添加一个 public 的属性访问器以暴露这个依赖属性,稍后会介绍如何给该依赖属性绑定一个 CLR 属性,当然你也可以自己写一个 CLR 属性来封装该依赖属性的值,但这样做会影响我们使用 Binding 等功能。
而 static 和 readonly 是必须的,所以即使你创建了一百个依赖对象,也仅占一个空间,当然这可能有点难以理解,静态的和只读的与我们正常使用依赖属性的情况完全不符,不过你可以暂时或略这里,这样写的原因不是本文想要讲的,我会在之后的博客里讲为什么这样写。
然后是 DependencyProperty ,这是依赖属性类,所以后面的ContentProperty 就是这个对象了。这里有一个命名规则,就是依赖属性的对象名都已 Peoperty 结尾,这样就表明他是一个依赖属性,方便他人阅读和理解。
你会发现我们并不是用构造函数来创建依赖属性的,而是使用了DependencyProperty 类中的一个静态方法,来创建一个依赖属性。该方法有多个重载,详细请参见 MSDN 文档,这里使用的是最简单的重载,有三个参数:第一个是一个 String 对象,值为给这个依赖属性绑定的 CLR 属性;第二个为 Type 类型,表明这个依赖属性存储的值是什么类型,注意是这个依赖属性存储的值,这个依赖属性的类型永远是DependencyProperty ;第三个参数也是 Type 类型,表明拥有该依赖属性的类是哪一个,也可以说把这个依赖属性注册到哪个类里,所以用 Register 这个名字就显得合理了。
下面来看一个稍完整的示例:
首先创建一个 WPF 项目,并创建一个类,名为 Test 。MainWindow.xaml 中代码如下:
<Window x:Class="wpfblog.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <TextBox x:Name="textBox" Grid.Row="0"></TextBox> <Button x:Name="button" Click="button_Click" Grid.Row="1">点击显示 Content</Button> </Grid> </Window>
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; namespace Blog_DependencyProperty { public class Test : DependencyObject { public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content",typeof(Test)); public Test() { } } }
如何为其添加 Binding 呢?MainWindow.xaml.cs代码如下 :
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; 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.Navigation; using System.Windows.Shapes; namespace Blog_DependencyProperty { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { Test test; public MainWindow() { InitializeComponent(); test = new Test(); Binding binding = new Binding("Text") { Source = textBox }; BindingOperations.SetBinding(test,Test.ContentProperty,binding); } private void button_Click(object sender,RoutedEventArgs e) { MessageBox.Show((string)test.GetValue(Test.ContentProperty)); } } }
重点在构造函数里,其中第一行代码不必说了,第二行创建了一个 Test 实例,第三行则是添加绑定,数据源是在 XAML 文件中创建的 TextBox 对象,Path 是 Text 属性(这个Text 属性其实是一个 CLR 属性,真正的依赖属性其实是 TextProperty),这里就不再赘述 Path 等概念了;第四行代码是给数据源和数据目标之间添加绑定,如果你使用 test.SetBinding 方法,你会发现微软没有在 DependencyObject 类中提供这个方法,所以你需要使用如第四行所示的代码来建立一个绑定,数据目标是 test 对象,依赖属性是ContentProperty ,第三个参数就是之前实例化的 binding 了。其实你应该觉得奇怪,数据目标是 test 对象,一般来说应该是以后台代码为数据源,前台 UI 控件为数据目标,这里反了过来,且 test 对象没有SetBinding 方法,需要使用BindingOperations 类中的静态方法,这也表达了微软希望 UI 控件作为数据目标,实现数据驱动 UI 的思想(读者也可以从类的层级方面发现问题)。
而后面给按钮添加的事件处理函数很好理解。利用从DependencyObject 对象继承而来的 GetValue 和 SetValue 方法可以读取或设置依赖属性的值。注意 GetValue 的方法返回值是 object 类型,所以你需要手动转换为你需要的类型。SetValue 方法稍后再讲。
好了,运行一下程序,在 TextBox 中输入内容,点击按钮,就会显示出你输入的内容。
内容就请大家忽略吧,最近博主有点感情受挫。
好了,言归正传,你会发现这里并没有用到创建(或者说注册更合适)依赖属性时的第一个参数,是的,即使没有 CLR 属性,也一样可以完美运行。
那么为什么我们还要有 CLR 属性来封装这个依赖属性的值呢?读者可以试下:
textBox.Text = Test.ContentProperty; textBox.Text = (string)Test.ContentProperty;
这两行代码都会报错,因为无法将一个DependencyProperty 类型的对象转换为 string ,当然有无法转为其他一些类型,而我们要想使其作为数据源的 Path 的话(之前是数据目标),就必须要得到它的值,你无法将一个函数用作 Path 吧,所以我们需要一个 CLR 属性。当然,使用 CLR 属性也更符合面向对象设计的一些思想,我们使用也更加方便。
public string Content { get { return (string)GetValue(ContentProperty); } set { SetValue(ContentProperty,value); } }
在MainWindow.xaml.cs 中,删掉按钮的那个事件处理函数,然后在构造函数中添加如下代码:
textBlock.SetBinding(TextBlock.TextProperty,new Binding("Content") { Source = test });
好了,来让我们运行下吧。什么?等等?还没实现 Inotifypropertychanged 接口?不管怎么样,先让我们运行看看吧。
没错,像这样创建的依赖属性可以直接作为数据源使用。上面这个例子已经证明了现在我们自己创建的这个依赖属性既可以作为数据目标也可以作为数据源。
好了,本篇博客就讲到这里,谢谢大家!欢迎大家批评指正。
Remember !You Make Luck !