WPF快速入门系列(4)——深入解析WPF绑定
一、引言
WPF绑定使得原本需要多行代码实现的功能,现在只需要简单的XAML代码就可以完成之前多行后台代码实现的功能。WPF绑定可以理解为一种关系,该关系告诉WPF从一个源对象提取一些信息,并将这些信息来设置目标对象的属性。目标属性总是依赖属性。然而,源对象可以是任何内容,可以是一个WPF元素、或ADO.NET数据对象或自定义的数据对象等。下面详细介绍了WPF绑定中的相关知识点。
二、绑定元素对象
2.1 如何实现绑定元素对象
这里首先介绍绑定最简单的情况——绑定元素对象,即数据源是一个WPF元素对象并且源属性是依赖属性。由于依赖属性具有内置的更改通知支持,因此,当在源对象中改变依赖属性的值时,会立即更新目标对象中的绑定属性。下面通过一个简单的例子来演示下如何绑定元素对象。具体的XAML代码(这里不需要后台代码)如下所示:
<StackPanel>
<Slider Name="sliderFontSize" Margin="3"
Minimum="1" Maximum="40" Value="10" TickFrequency="1" TickPlacement="TopLeft"/>
<TextBlock Margin="10" Text="LearningHard" Name="lbtext"
FontSize="{Binding ElementName=sliderFontSize, Path=Value}"></TextBlock>
</StackPanel>
在上面XAML代码中,TextBlock控件的FontSize属性绑定了Slider控件的Value属性,感觉说绑定有点拗口,你可以直接理解为TextBlock的FontSize属性的值来自与Slider控件的Value值,由于源属性Value是依赖属性,具体内置的更改通知功能,所以Slider控件Value值的改变,直接影响TextBlock控件FontSize的值。正如我们分析的那样,实际运行结果也是如此,运行结果如下图所示:

当移动上图中Slider控件上的游标时,下面的文本字体大小也会跟着一起改变。具体效果这里就不贴图了,大家可以自行尝试。从中可以看到WPF绑定的强大了吧,如果放到以前WinForm开发中,你需要监听Slider的ValueChanged事件,然后在事件处理程序中去动态改变文本的字体大小。
这里Path除了可以直接绑定属性之外,还可以绑定属性的属性,如FontFamily.Source,也可以指向属性使用的索引器,如Content.Children[0]。当然你也可以执行多层次的路径,如指向属性的属性的属性等。
另外,如果绑定失败时,WPF不会引发异常来告知绑定失败的原因。例如,指定的元素或属性不存在,此时不会收到任何提示,只是在目标属性不能显示数据罢了。然而在调试模式下,你可以在输出窗口来查看绑定失败的信息,例如,在上面XAML代码,我们绑定Slider控件一个不存在的属性,如Text属性,此时在Output窗口中就会看到如下信息:

2.2 绑定模式
绑定的一个最大的特点就是源属性改变时,目标属性会自动地更新。然而上面的示例有一个问题,即目标对象的改变不会自动更新源对象的属性。通过下面的例子可以看出这个问题所在。此时XAML代码修改为:
<Window x:Class="WPFBindingDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="400">
<StackPanel>
<Slider Name="sliderFontSize" Margin="3"
Minimum="1" Maximum="40" Value="10" TickFrequency="1" TickPlacement="TopLeft"/>
<TextBlock Margin="10" Text="LearningHard" Name="lbtext"
FontSize="{Binding ElementName=sliderFontSize, Path=Value}"></TextBlock> <!--在按钮的Click事件处理程序中去改变目标对象的FontSize的值-->
<StackPanel Orientation="Horizontal">
<Button Margin="10" Padding="5" Click="cmd_SetSmall">Set to Small</Button>
<Button Margin="10" Padding="5" Click="cmd_SetNormal">Set to Normal</Button>
<Button Margin="10" Padding="5" Click="cmd_SetLarge">Set to Large</Button>
</StackPanel>
</StackPanel>
</Window>
此时后台C#代码如下所示:
private void cmd_SetSmall(object sender, RoutedEventArgs e)
{
// 仅仅在双向模式下工作
lbtext.FontSize = ;
} private void cmd_SetNormal(object sender, RoutedEventArgs e)
{
sliderFontSize.Value = 20;
} private void cmd_SetLarge(object sender, RoutedEventArgs e)
{
// 仅仅在双向模式下工作
lbtext.FontSize = ;
}
具体的运行效果如下图所示:

从上图可以看到,当在后台更改TextBlock的FontSize属性值,而Slider的Value值却没有进行更新。此时,你肯定会想问,能不能实现目标属性的更变也会自动改变绑定中源属性的机制呢?因为这样就不会显得那样呆板了,然而,你想到的了WPF团队肯定也想到了,WPF支持双向绑定,即从源到目标以及目标到源,要支持双向绑定,只需要设置Binding对象的Mode属性为TwoWay即可,修改后的XAML代码为:
<StackPanel>
<Slider Name="sliderFontSize" Margin="3"
Minimum="1" Maximum="40" Value="10" TickFrequency="1" TickPlacement="TopLeft"/>
<TextBlock Margin="10" Text="LearningHard" Name="lbtext"
FontSize="{Binding ElementName=sliderFontSize, Path=Value, Mode=TwoWay}"></TextBlock> <!--在按钮的Click事件处理程序中去改变目标对象的FontSize的值-->
<StackPanel Orientation="Horizontal">
<Button Margin="10" Padding="5" Click="cmd_SetSmall">Set to Small</Button>
<Button Margin="10" Padding="5" Click="cmd_SetNormal">Set to Normal</Button>
<Button Margin="10" Padding="5" Click="cmd_SetLarge">Set to Large</Button>
</StackPanel>
</StackPanel>
Mode属性除了可以设置OneWay,TwoWay值外,还可以设置Default、OneTime和OneWayToSource,关于这些值更详细的介绍请自行参考MSDN:http://msdn.microsoft.com/zh-cn/library/system.windows.data.bindingmode(v=vs.110).aspx。
另外,除了可以在XAML中通过Binding标记地方式声明绑定外,还可以使用代码方式动态创建绑定。如上面的例子中代码创建绑定的实现代码如下所示:
Binding binding = new Binding();
binding.Source = sliderFontSize;
binding.Path = new PropertyPath("Value");
binding.Mode = BindingMode.TwoWay;
lbtext.SetBinding(TextBlock.FontSizeProperty, binding);
还可以通过使用BindingOperations类的ClearBinding方法来移除数据绑定。还可以使用ClearAllBindings移除一个元素的所有数据绑定。
2.3 绑定更新
在上面的例子中,还存在另一个问题,当通过在文本框中输入内容来改变显示的字体尺寸时,此时什么事情都不会发生,知道使用tab键将焦点转移到另外一个控件时,才会应用对应的改变。此时XAML代码如下所示:
<Window x:Class="WPFBindingDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="400">
<StackPanel>
<Slider Name="sliderFontSize" Margin="3"
Minimum="1" Maximum="40" Value="10" TickFrequency="1" TickPlacement="TopLeft"/>
<TextBlock Margin="10" Text="LearningHard" Name="lbtext"
FontSize="{Binding ElementName=sliderFontSize, Path=Value, Mode=TwoWay}"></TextBlock> <!--在按钮的Click事件处理程序中去改变目标对象的FontSize的值-->
<StackPanel Orientation="Horizontal">
<Button Margin="10" Padding="5" Click="cmd_SetSmall">Set to Small</Button>
<Button Margin="10" Padding="5" Click="cmd_SetNormal">Set to Normal</Button>
<Button Margin="10" Padding="5" Click="cmd_SetLarge">Set to Large</Button>
</StackPanel> <!--添加一个输入文本框来设置文本字体大小进行测试问题-->
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock VerticalAlignment="Center">Set FontSize:</TextBlock>
<TextBox Text="{Binding ElementName=lbtext, Path=FontSize, Mode=TwoWay}" Width="100"/>
</StackPanel>
</StackPanel>
</Window>
后台代码实现与前面的一样,此时运行的效果如下图所示:

为了明白导致这个问题的原因,这里需要深入分析下绑定表达式。当使用OneWay或TwoWay绑定时,改变后的值会立即从源传播到目标。对于滑动条,然而,从目标到源传播未必会立即发生。因为,它们的行为是由Binding.UpdateSourceTrigger属性控制,该属性可以使用下图列出的某个值。注意,UpdateSourceTrigger属性值并不影响目标的更新方式,它仅仅控制TwoWay模式或OneWayToSource模式的绑定更新源的方式。而文本框正是使用LostFocus方式从目标向源进行更新的。

既然,找出了导致原因,此时可以对XAML代码进行修改,使得当用于在文本框中输入内容时将变化应用于字体尺寸,具体改变部分的XAML代码为:
<!--添加一个输入文本框来设置文本字体大小进行测试问题-->
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock VerticalAlignment="Center">Set FontSize:</TextBlock>
<TextBox Text="{Binding ElementName=lbtext, Path=FontSize, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
</StackPanel>
另外,需要注意的是,TextBox的Text属性的默认行为是LostFocus,这是因为当用于输入内容时,文本框中文本会不断变化,从而引起多次更新。所以PropertyChanged模式可能会使应用程序运行更缓慢,所以LostFocus默认行为可以说是合理的。
要完全控制源对象的更新时机,也可以选择UpdateSourceTrigger.Explicit模式。此时就需要额外编写代码手动触发更新,此时可以添加一个Apply按钮,并在按钮的Click事件处理程序中调用BindingExpression.UpdateSource方法触发立即刷新并更新字体大小的操作。具体的实现代码如下所示:
// 获得应用于文本框上的绑定
BindingExpression be = txtFontSize.GetBindingExpression(TextBox.TextProperty);
// 调用UpdateSource更新源对象
be.UpdateSource();
三、绑定非元素对象
上面都是介绍如何链接两个元素的绑定,但是在数据驱动的应用程序中,更常见的情况是创建从一个对象中提起数据的绑定表达式。不过希望绑定的信息必须存储在一个公有属性中。因为WPF绑定不能获取私有信息或公有字段。
当绑定一个非元素对象时,不能使用Binding.ElementName属性,但可以使用以下属性中的一个:
- Source——该属性是指向源对象的引用,即提供数据的对象。
- RelativeSource——该属性使用RelativeSource对象指定绑定源的相对位置,默认值为null。
- DataContext属性——如果没有使用Source或RelativeSource属性指定一个数据源,WPF会从当前元素开始在元素树中向上查找。检查每个元素的DataContext属性,并使用第一个非空的DataContext属性。当然你也可以自己设置DataContext属性。
下面通过一个例子来演示下如何绑定到非元素对象。下面的演示如何使用DataContext属性来绑定一个自定义对象的属性。首先自定义一个实现了INotifyPropertyChanged接口的类。这个接口是为了发出属性更改的通知,即实现了这个接口将会实现当源对象的公共属性发生改变时,该属性的值会立即响应到界面上显式。当然不实现这个接口的对象也可以绑定控件中,只要被绑定是公有属性就可以。具体的实现代码如下所示:
using System.ComponentModel; namespace WPFBindingDemo
{
public class Student:INotifyPropertyChanged
{
private int m_ID;
private string m_StudentName;
private double m_Score; public int ID
{
get { return m_ID; }
set
{
if (value != m_ID)
{
m_ID = value;
Notify("ID");
}
}
} public string StudentName
{
get { return m_StudentName; }
set
{
if (value != m_StudentName)
{
m_StudentName = value;
Notify("StudentName");
}
}
} public double Score
{
get { return m_Score; }
set
{
if (value != m_Score)
{
m_Score = value;
Notify("Score");
}
}
} public event PropertyChangedEventHandler PropertyChanged;
private void Notify(string propertyName)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
既然源数据对象以准备好了,自然接下来就是去设计WPF界面来让控件来绑定这个源对象了,具体的XAML代码如下所示:
<Window x:Class="WPFBindingDemo.BindingToCollection"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BindingToCollection" Height="300" Width="300">
<StackPanel Margin="50">
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="学号:" />
<TextBlock Text="{Binding Path=ID}" Width="100"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="姓名:" />
<TextBlock Text="{Binding Path=StudentName}" Width="100"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="分数:" />
<TextBlock Text="{Binding Path=Score}" Width="100"/>
</StackPanel> <StackPanel Orientation="Horizontal" Margin="10">
<Button Content="改变姓名" Name="changeName" Click="changeName_Click_1"/>
<Button Content="改变分数" Name="changeScore" Margin="20,0,0,0" Click="ChangeScore_Click"/>
</StackPanel>
</StackPanel>
</Window>
对应的后台代码逻辑如下所示:
public partial class BindingToCollection : Window
{
private Student m_student;
public BindingToCollection()
{
InitializeComponent();
m_student = new Student() { ID = , StudentName = "LearningHard", Score = };
// 设置Window对象的DataContext属性
this.DataContext = m_student ;
} private void ChangeScore_Click(object sender, RoutedEventArgs e)
{
m_student.Score = ;
} private void changeName_Click_1(object sender, RoutedEventArgs e)
{
m_student.StudentName = "Learning";
}
}
完成了示例所有代码的编写之后,下面具体看看示例的运行效果,看看是否可以成功完成绑定并源对象的属性的更改会立即反应到界面中,具体的效果图如下所示:

从上图示例的演示动画效果可以看出,上面的代码确实实现我们预期的功能。从上面代码可以看出,我们并没有对每个控件单独设置它的Source属性,而是直接设置了Window对象的DataContext属性。这样绑定的控件发现没有设置source属性或RelativeSource属性,就会从元素树中查找DataContex属性不为null的值来作为自己的DataContext。通过这样的方式可以省去重复在多个控件中设置相同的DataContext属性。
这里只是演示了绑定单个数据对象的情况,就如之前所说的,数据源还可以是XAML文件,ADO.NET数据对象、集合等,这里就不一一实现了,只要了解具体思路,具体问题具体搜索解决就好了。这里给出两个非常的好例子。
Simple Demo of Binding to a Database in WPF using LINQ-SQL
How to Perform WPF Data Binding Using LINQ to XML
四、提高大列表的性能
如果绑定的数据源具有大量记录时,此时就需要考虑性能的问题了。然而,幸运的是,WPF很多列表控件都已经帮我们做好了相应的支持,这里只是提出来让大家知道这点。
对于大列表显示性能问题,WPF做了以下几种支持:
- UI虚拟化——UI虚拟化是列表仅为当前显示项创建容器对象的一种技术,例如,如果有一个具有5万条记录的列表,但是可见区域只能包含30条记录,ListBox控件只创建30个ListBoxItem对象。如果ListBox控件不支持UI虚拟化的话,它将需要生成全部5万个ListBoxItem对象,这显然需要占用更多的内存。并且分配这些对象的时间用户明显可以感觉到,这就带来了非常不好的用户体验。WPF中UI虚拟化是通过VirtualizingStackPanel容器实现的。像ListBox、ListView和DataGrid都自动使用VirtualizingStackPanel面板布局它们的子元素,所以,这些控件都默认支持虚拟化功能。然而,ComboBox需要支持虚拟化支持,必须明确提供新的ItemPanelTemplate添加虚拟化支持,具体实现如下所示:
<ComboBox>
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel></VirtualizingStackPanel>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
TreeView控件也支持虚拟化,但它在默认情况下,关闭了该支持,你需要显式启用该特性,具体使用的启用代码如下所示:
<TreeView VirtualizingPanel.IsVirtualizing="True" />
- 项目容器再循环——WPF 3.5 SP1使用项目容器再循环改进了虚拟化。通常支持虚拟化的列表在滚动时,控件不断地创建新的项目容器对象来保存新的可见项。例如,当具有5万个项的ListBox控件,在滚动时,ListBox需要重新生成新的ListBoxItem对象。但是如果启用了项目容器再循环,ListBox控件会保存少量ListBoxItem对象存活,当滚动时,将新数据加到这些之前的ListBoxItem对象,从而重复使用它们。具体支持代码如下所示
<ListBox VirtualizingPanel.VirtualizationMode="Recycling"/>
项目容器再循环提供了滚动性能,并降低了内存消耗量,因为垃圾回收器不需要查找旧对象进行回收。为了确保向后兼容,除了DataGrid之后的所有列表控件默认都禁用该特性,如需支持,需要显式启用。
- 延迟滚动——为了进一步提供滚动性能,可以开启延迟滚动功能。使用延迟滚动,当用户在滚动条上拖动滚动滑块时不更新列表显示,只有用户释放了滚动滑块时才刷新。当使用常规滚动时,在拖动的同时会刷新列表,使列表显示正在改变的位置。这个特性也需要显式启用,启用代码如下:
<ListBox ScrollViewer.IsDeferredScrollingEnabled="True"/>
显然,需要在响应性和易用性之间平衡。如果有一个复杂的模板和大量数据,对于提高速度可能会选择使用延迟滚动特性,但当用户需要在滚动时查看目前滚动位置,则就可以不启用该特性。
上面介绍了这么多,其实提供列表控件的性能主要在两方面:UI虚拟化提高了列表项初始化的时间,因为UI虚拟化支持一次性不初始化所有项,而在滚动是自动创建新的项。项目容器再循环和延迟滚动提高了滚动性能。
另外WPF绑定还有两个知识点:数据验证和数据转换,对于数据验证与Asp.net中验证类似,都是为了保证输入数据的合法性,而数据转换指的是在源数据绑定到目标依赖属性之前要做对应的转换,例如WPF显示人民币都需要显示一个¥符号,但是如果数据源的内容只是“120”这样的字符串怎么办呢?这时候就可以通过数据转换在绑定之前,把数据源的值转换成显示所需要的格式。对于这两个知识点,我觉得在遇到问题时再去学就好了,因为我们已经明白了解决问题的思路了。所以,在快速入门系列中不想太深入的介绍这两个知识点,以使大家可以快速掌握WPF要领。这里给出几个学习链接:
数据绑定概述
WPF Data Binding - Part 1
WPF Simple Data Converter Example
五、小结
到这里,这篇博文的内容就介绍结束了,时间不知不觉的已经2点多了。下面一篇博文将分享WPF命令的内容。
本文所有源码下载:WPFBindingDemo.zip
WPF快速入门系列(4)——深入解析WPF绑定的更多相关文章
- WPF快速入门系列(5)——深入解析WPF命令
一.引言 WPF命令相对来说是一个崭新的概念,因为命令对于之前的WinForm根本没有实现这个概念,但是这并不影响我们学习WPF命令,因为设计模式中有命令模式,关于命令模式可以参考我设计模式的博文:h ...
- WPF快速入门系列(3)——深入解析WPF事件机制
一.引言 WPF除了创建了一个新的依赖属性系统之外,还用更高级的路由事件功能替换了普通的.NET事件. 路由事件是具有更强传播能力的事件——它可以在元素树上向上冒泡和向下隧道传播,并且沿着传播路径被事 ...
- WPF快速入门系列(7)——深入解析WPF模板
一.引言 模板从字面意思理解是“具有一定规格的样板".在现实生活中,砖块都是方方正正的,那是因为制作砖块的模板是方方正正的,如果我们使模板为圆形的话,则制作出来的砖块就是圆形的,此时我们并不 ...
- WPF快速入门系列(2)——深入解析依赖属性
一.引言 感觉最近都颓废了,好久没有学习写博文了,出于负罪感,今天强烈逼迫自己开始更新WPF系列.尽管最近看到一篇WPF技术是否老矣的文章,但是还是不能阻止我系统学习WPF.今天继续分享WPF中一个最 ...
- WPF快速入门系列(1)——WPF布局概览
一.引言 关于WPF早在一年前就已经看过<深入浅出WPF>这本书,当时看完之后由于没有做笔记,以至于我现在又重新捡起来并记录下学习的过程,本系列将是一个WPF快速入门系列,主要介绍WPF中 ...
- WPF快速入门系列(8)——MVVM快速入门
一.引言 在前面介绍了WPF一些核心的内容,其中包括WPF布局.依赖属性.路由事件.绑定.命令.资源样式和模板.然而,在WPF还衍生出了一种很好的编程框架,即WVVM,在Web端开发有MVC,在WPF ...
- .Net5 WPF快速入门系列教程
一.概要 在工作中大家会遇到需要学习新的技术或者临时被抽调到新的项目当中进行开发.通常这样的情况比较紧急没有那么多的时间去看书学习.所以这里向wpf技术栈的开发者分享一套wpf教程,基于.net5框架 ...
- WPF快速入门系列(9)——WPF任务管理工具实现
转载自:http://www.cnblogs.com/shanlin/p/3954531.html WPF系列自然需要以一个实际项目为结束.这里分享一个博客园博客实现的一个项目,我觉得作为一个练手的项 ...
- WPF快速入门系列(6)——WPF资源和样式
一.引言 WPF资源系统可以用来保存一些公有对象和样式,从而实现重用这些对象和样式的作用.而WPF样式是重用元素的格式的重要手段,可以理解样式就如CSS一样,尽管我们可以在每个控件中定义格式,但是如果 ...
随机推荐
- dubbo问题总结
1.dubbo序列化数据丢失问题.dubbo的返回结果是对象,那么必须要实现对象中需要返回的数据的get方法. 2.dubbo序列化问题.
- 多台web如何共享session进行存储(转载)
session的存储了解以前是怎么做的,搞清楚了来龙去脉,才会明白进行共享背后的思想和出发点.我喜欢按照这样的方式来问(或者去搞清楚):为什么要session要进行共享,不共享会什么问题呢? php中 ...
- maven自动部署到远程tomcat教程
使用maven的自动部署功能可以很方便的将maven工程自动部署到远程tomcat服务器,节省了大量时间. 本文章适用于tomcat的7.x ,8.x, 9.x版本. 下面是自动部的步骤 1,首先,配 ...
- CSS布局--浮动与清除
浮动和清除 浮动和清除是页面布局的重要属性.浮动的意思是指将元素从常规的文档流中取出来. 当你浮动一个元素的时候,浮动的元素会被浏览器尽量的往上放,能放多高就放多高,一直到某个元素的边界为止. 浮动元 ...
- 【C#】 一些不常用,很容易混淆的知识点
[C#] 一些不常用但很容易混淆的知识点 1. 访问修饰符 internal ,译为内部的, 在同一个程序集中可访问,它的内部是相对与程序集的,可不能想当然了 2. String.Compare 这个 ...
- JAVA学习<四>
一. 数组: Java 中操作数组只需要四个步骤: 1. 声明数组 语法: 数据类型[ ] 数组名: 或者 数据类型 数组名[ ]: 其中,数组名可以是任意合法的变量名. 2. 分配空间 简单地 ...
- instancetype、id、NSObject的联系和区别
1.id和instancetype都能省去具体类型,提高代码的通用性.而NSObject *则没有这种功能. 2.instancetype只能用于方法的返回类型,而id用处和NSObject *类似. ...
- [转] mhvtl虚拟磁带库的安装与应用
转自:candon123 -- http://candon123.blog.51cto.com/704299/388192/ 1.获取mhvtl: 官方网站:http://mhvtl.nimsa.u ...
- shell脚本学习--shell中的变量$
$$ :Shell本身的PID(ProcessID) $! :Shell最后运行的后台Process的PID $? :最后运行的命令的结束代码(返回值) $- :使用Set命令设定的Flag一览 $* ...
- main与对象初始化 in C++
没有学过代码编译的原理,以前也没有兴趣去学编译器的相关原理,但是近期通过阅读google开源项目gtest,对我稍有触动. 代码: main test示例 TEST宏定义 #define TEST(t ...