WPF快速入门系列(4)——深入解析WPF绑定

最后更新于:2022-04-02 00:12:55

# WPF快速入门系列(4)——深入解析WPF绑定 ## 一、引言 WPF绑定使得原本需要多行代码实现的功能,现在只需要简单的XAML代码就可以完成之前多行后台代码实现的功能。WPF绑定可以理解为一种关系,该关系告诉WPF从一个源对象提取一些信息,并将这些信息来设置目标对象的属性。目标属性总是依赖属性。然而,源对象可以是任何内容,可以是一个WPF元素、或ADO.NET数据对象或自定义的数据对象等。下面详细介绍了WPF绑定中的相关知识点。 ## 二、绑定元素对象 ## 2.1 如何实现绑定元素对象 这里首先介绍绑定最简单的情况——绑定元素对象,即数据源是一个WPF元素对象并且源属性是依赖属性。由于依赖属性具有内置的更改通知支持,因此,当在源对象中改变依赖属性的值时,会立即更新目标对象中的绑定属性。下面通过一个简单的例子来演示下如何绑定元素对象。具体的XAML代码(这里不需要后台代码)如下所示: 在上面XAML代码中,TextBlock控件的FontSize属性绑定了Slider控件的Value属性,感觉说绑定有点拗口,你可以直接理解为TextBlock的FontSize属性的值来自与Slider控件的Value值,由于源属性Value是依赖属性,具体内置的更改通知功能,所以Slider控件Value值的改变,直接影响TextBlock控件FontSize的值。正如我们分析的那样,实际运行结果也是如此,运行结果如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-23_56a2eb4250fbc.png) 当移动上图中Slider控件上的游标时,下面的文本字体大小也会跟着一起改变。具体效果这里就不贴图了,大家可以自行尝试。从中可以看到WPF绑定的强大了吧,如果放到以前WinForm开发中,你需要监听Slider的ValueChanged事件,然后在事件处理程序中去动态改变文本的字体大小。 这里Path除了可以直接绑定属性之外,还可以绑定属性的属性,如FontFamily.Source,也可以指向属性使用的索引器,如Content.Children[0]。当然你也可以执行多层次的路径,如指向属性的属性的属性等。 另外,如果绑定失败时,WPF不会引发异常来告知绑定失败的原因。例如,指定的元素或属性不存在,此时不会收到任何提示,只是在目标属性不能显示数据罢了。然而在调试模式下,你可以在输出窗口来查看绑定失败的信息,例如,在上面XAML代码,我们绑定Slider控件一个不存在的属性,如Text属性,此时在Output窗口中就会看到如下信息: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-23_56a2eb4263332.png) ## 2.2 绑定模式 绑定的一个最大的特点就是源属性改变时,目标属性会自动地更新。然而上面的示例有一个问题,即目标对象的改变不会自动更新源对象的属性。通过下面的例子可以看出这个问题所在。此时XAML代码修改为: ``` ``` 此时后台C#代码如下所示: ``` private void cmd_SetSmall(object sender, RoutedEventArgs e) { // 仅仅在双向模式下工作 lbtext.FontSize = 5; } private void cmd_SetNormal(object sender, RoutedEventArgs e) { sliderFontSize.Value = 20; } private void cmd_SetLarge(object sender, RoutedEventArgs e) { // 仅仅在双向模式下工作 lbtext.FontSize = 40; } ``` 具体的运行效果如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-23_56a2eb42814d8.gif) 从上图可以看到,当在后台更改TextBlock的FontSize属性值,而Slider的Value值却没有进行更新。此时,你肯定会想问,能不能实现目标属性的更变也会自动改变绑定中源属性的机制呢?因为这样就不会显得那样呆板了,然而,你想到的了WPF团队肯定也想到了,WPF支持双向绑定,即从源到目标以及目标到源,要支持双向绑定,只需要设置Binding对象的[Mode](http://msdn.microsoft.com/zh-cn/library/system.windows.data.binding.mode(v=vs.110).aspx)属性为TwoWay即可,修改后的XAML代码为: ``` ``` Mode属性除了可以设置OneWay,TwoWay值外,还可以设置Default、OneTime和OneWayToSource,关于这些值更详细的介绍请自行参考MSDN:[http://msdn.microsoft.com/zh-cn/library/system.windows.data.bindingmode(v=vs.110).aspx](http://msdn.microsoft.com/zh-cn/library/system.windows.data.bindingmode(v=vs.110).aspx)。 另外,除了可以在XAML中通过Binding标记地方式声明绑定外,还可以使用代码方式动态创建绑定。如上面的例子中代码创建绑定的实现代码如下所示: 还可以通过使用[BindingOperations](http://msdn.microsoft.com/zh-cn/library/system.windows.data.bindingoperations(v=vs.110).aspx)类的[ClearBinding](http://msdn.microsoft.com/zh-cn/library/system.windows.data.bindingoperations.clearbinding(v=vs.110).aspx)方法来移除数据绑定。还可以使用[ClearAllBindings](http://msdn.microsoft.com/zh-cn/library/system.windows.data.bindingoperations.clearallbindings(v=vs.110).aspx)移除一个元素的所有数据绑定。 ## 2.3 绑定更新 在上面的例子中,还存在另一个问题,当通过在文本框中输入内容来改变显示的字体尺寸时,此时什么事情都不会发生,知道使用tab键将焦点转移到另外一个控件时,才会应用对应的改变。此时XAML代码如下所示: ``` Set FontSize: ``` 后台代码实现与前面的一样,此时运行的效果如下图所示: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-23_56a2eb4297e17.gif) 为了明白导致这个问题的原因,这里需要深入分析下绑定表达式。当使用OneWay或TwoWay绑定时,改变后的值会立即从源传播到目标。对于滑动条,然而,从目标到源传播未必会立即发生。因为,它们的行为是由[Binding.UpdateSourceTrigger](http://msdn.microsoft.com/zh-cn/library/system.windows.data.binding.updatesourcetrigger(v=vs.110).aspx)属性控制,该属性可以使用下图列出的某个值。**注意,UpdateSourceTrigger属性值并不影响目标的更新方式,它仅仅控制TwoWay模式或OneWayToSource模式的绑定更新源的方式。**而文本框正是使用LostFocus方式从目标向源进行更新的。 ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-23_56a2eb42a9c68.png) 既然,找出了导致原因,此时可以对XAML代码进行修改,使得当用于在文本框中输入内容时将变化应用于字体尺寸,具体改变部分的XAML代码为: 另外,需要注意的是,TextBox的Text属性的默认行为是LostFocus,这是因为当用于输入内容时,文本框中文本会不断变化,从而引起多次更新。所以PropertyChanged模式可能会使应用程序运行更缓慢,所以LostFocus默认行为可以说是合理的。 要完全控制源对象的更新时机,也可以选择UpdateSourceTrigger.Explicit模式。此时就需要额外编写代码手动触发更新,此时可以添加一个Apply按钮,并在按钮的Click事件处理程序中调用[BindingExpression.UpdateSource](http://msdn.microsoft.com/zh-cn/library/system.windows.data.updatesourcetrigger(v=vs.110).aspx?query=UpdateSource)方法触发立即刷新并更新字体大小的操作。具体的实现代码如下所示: ## 三、绑定非元素对象 上面都是介绍如何链接两个元素的绑定,但是在数据驱动的应用程序中,更常见的情况是创建从一个对象中提起数据的绑定表达式。不过希望绑定的信息必须存储在一个公有属性中。因为WPF绑定不能获取私有信息或公有字段。 当绑定一个非元素对象时,不能使用Binding.ElementName属性,但可以使用以下属性中的一个: * [Source](http://msdn.microsoft.com/zh-cn/library/system.windows.data.binding.source(v=vs.110).aspx)——该属性是指向源对象的引用,即提供数据的对象。 * [RelativeSource](http://msdn.microsoft.com/zh-cn/library/system.windows.data.binding.relativesource(v=vs.110).aspx)——该属性使用RelativeSource对象指定绑定源的相对位置,默认值为null。 * DataContext属性——如果没有使用Source或RelativeSource属性指定一个数据源,WPF会从当前元素开始在元素树中向上查找。检查每个元素的DataContext属性,并使用第一个非空的DataContext属性。当然你也可以自己设置DataContext属性。 下面通过一个例子来演示下如何绑定到非元素对象。下面的演示如何使用DataContext属性来绑定一个自定义对象的属性。首先自定义一个实现了[INotifyPropertyChanged](http://msdn.microsoft.com/zh-cn/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx)接口的类。这个接口是为了发出属性更改的通知,即实现了这个接口将会实现当源对象的公共属性发生改变时,该属性的值会立即响应到界面上显式。当然不实现这个接口的对象也可以绑定控件中,只要被绑定是公有属性就可以。具体的实现代码如下所示: ``` 1 using System.ComponentModel; 2 3 namespace WPFBindingDemo 4 { 5 public class Student:INotifyPropertyChanged 6 { 7 private int m_ID; 8 private string m_StudentName; 9 private double m_Score; 10 11 public int ID 12 { 13 get { return m_ID; } 14 set 15 { 16 if (value != m_ID) 17 { 18 m_ID = value; 19 Notify("ID"); 20 } 21 } 22 } 23 24 public string StudentName 25 { 26 get { return m_StudentName; } 27 set 28 { 29 if (value != m_StudentName) 30 { 31 m_StudentName = value; 32 Notify("StudentName"); 33 } 34 } 35 } 36 37 public double Score 38 { 39 get { return m_Score; } 40 set 41 { 42 if (value != m_Score) 43 { 44 m_Score = value; 45 Notify("Score"); 46 } 47 } 48 } 49 50 public event PropertyChangedEventHandler PropertyChanged; 51 private void Notify(string propertyName) 52 { 53 if (PropertyChanged != null) 54 { 55 this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 56 } 57 } 58 } 59 } ``` 既然源数据对象以准备好了,自然接下来就是去设计WPF界面来让控件来绑定这个源对象了,具体的XAML代码如下所示: ```
';