一 概述
文章一开始,将给出一个使用WPF绑定的小实例。并以此为起点,逐步展开对WPF绑定知识的探讨。
二 实例演示
1新建WPF应用程序WpfBindingExp,下面是程序主画面的代码。
<pre name="code" class="html"><Window x:Class="WpfBindingExp.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 Height="2*"></RowDefinition>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="4*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Grid.Row="2" Orientation="Horizontal" Background="Aqua">
<TextBlock Text="学号:" Height="30" Margin="10,0,0,0"/>
<TextBlock x:Name="TextBlock_StudentId" Width="100" Height="30" Margin="0,0,50,0"/>
<TextBlock Text="姓名:" Height="30"/>
<TextBlock x:Name="TextBlock_StudentName" Width="100" Height="30"/>
</StackPanel>
</Grid>
</Window>
主画面被分割成4行,并在第3行放置容器控件StackPanel,再在容器控件中放入两个TextBlock控件,被命名为TextBlock_StudentId和TextBlock_StudentName,分别用来显示学生的学号和姓名信息。
2 在画面的后端代码中定义Student类,并定义类型为Student的属性,该属性被初始化为Id为1000,Name为“三五月儿”的学生对象。
using System.Windows;
namespace WpfBindingExp
{
public partial class MainWindow : Window
{
public Student Student { get; set; }
public MainWindow()
{
InitializeComponent();
Student = new Student() { Id = 10000, Name = "三五月儿" };
}
}
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
}
}
3 将Student对象的Id属性、Name属性分别与画面中的TextBlock_StudentId控件、TextBlock_StudentName控件的Text属性进行绑定,绑定使用以下代码完成。
this.TextBlock_StudentId.SetBinding(TextBlock.TextProperty, new Binding("Id") { Source = Student,Mode=BindingMode.TwoWay });
this.TextBlock_StudentName.SetBinding(TextBlock.TextProperty, new Binding("Name") { Source = Student,Mode=BindingMode.TwoWay });
4 执行程序,得到以下效果图。
图1 实例运行效果图
三 实例说明
下面将对实例代码进行分析说明。
1 控件的SetBinding方法
从下面这行代码开始吧。
this.TextBlock_StudentId.SetBinding(TextBlock.TextProperty, new Binding("Id") { Source = Student,Mode=BindingMode.TwoWay});
代码中调用TextBlock控件的SetBinding方法完成Student对象的Id属性与TextBlock控件的Text属性的绑定工作。
SetBinding方法接受两个参数:
- 第一个参数表示绑定的目标属性。这里传入的值为TextBlock.TextProperty,查看其定义,是一个类型为DependencyProperty的静态只读属性,说明它是一个依赖属性,只有依赖属性才可以作为绑定的目标属性。关于依赖属性,在这里不做过多说明。大家只要记住:作为绑定目标的属性必须是依赖属性。
- 第二个参数传入一个Binding对象。在构造这个Binding对象时,通过Binding类的构造函数传入绑定的源属性名,这里为“Id”(也可以通过设置Binding对象的Path属性来指定绑定对象的源属性名);随后通过设置Binding对象的Source属性来指定绑定的源对象,这里被设置为Student对象;再通过设置绑定的Mode属性来指定绑定的数据流动方向,这里被设置为TwoWay,表示双向绑定。构造好Binding对象后,就可以使用这个对象将源对象和目标对象关联起来,Binding对象是源对象与目标对象沟通的桥梁,也可以将其看成源对象和目标对象之间的数据传递通道。
2 Binding的Mode属性
关于Binding的Mode属性,这里稍微再啰嗦几句,Mode属性用来指定绑定的数据流动方向,该属性可以被设置为下列值之一:
- OneWay:使用OneWay绑定时,每当源发生变化,数据就会从源流向目标。
- OneTime: 绑定也会将数据从源发送到目标;但是,仅当启动了应用程序或DataContext发生更改时才会执行此操作,因此,它不会侦听源中的更改通知。
- OneWayToSource: 绑定会将数据从目标发送到源。
- TwoWay: 绑定会将源数据发送到目标,但如果目标属性的值发生变化,则会将它们发回给源。
- Default: Binding的模式根据实际情况来定,如果是可编辑的就是TwoWay,只读的就是OneWay。
3 做个小小的总结
可见,要想完成绑定,首先需要构造用于绑定的Binding对象,该对象包含绑定的源对象(由Soure属性指定),绑定的源属性(由Path属性指定,也可通过构造函数来指定)以及绑定的数据流动方向(由Mode属性指定),当然还可以使用Binding类提供的其他属性来完成更多的设置,这里就不做一一说明了。
4 换种玩法
我们还可以将绑定的代码翻译成以下代码。
Binding binding = new Binding();
binding.Source = Student;
binding.Path = new PropertyPath("Id");
binding.Mode = BindingMode.TwoWay;
this.TextBlock_StudentId.SetBinding(TextBlock.TextProperty, binding);
5 BindingOperations类的SetBinding方法
除了使用控件的SetBinding方法,还可以使用工具类BindingOperations的SetBinding方法来完成绑定操作:
BindingOperations.SetBinding(this.TextBlock_StudentId, TextBlock.TextProperty, binding);
工具类BindingOperations的SetBinding方法接收三个参数:
- 第一个参数指定绑定的目标对象;
- 第二个参数指定绑定的目标属性,必须是依赖属性;
- 第三个参数指定使用哪个Binding对象将源对象与目标对象联系起来。
6 Binding模型示意图
通过前面的学习,可以总结出WPF绑定模型的基本构成。
图2 Binding模型示意图
Binding模型包括:源对象、目标对象及Binding对象。这里的Binding对象使用的是双向箭头,表示双向绑定。
在WPF中,常常使用Binding模型将数据以绑定的形式与界面元素联系起来。
7 升级我们的Student对象
只要稍加修改Student类的定义,便可以使实例中的绑定功能更上一层楼。
在升级Student类前,需要引入名称空间System.ComponentModel,因为升级Student类用到的INotifyPropertyChanged接口的定义就位于该名称空间中。
下面是升级后的Student类的代码。
public class Student:INotifyPropertyChanged
{
private int id;
public int Id
{
get
{
return id;
}
set
{
id = value;
NotifyPropertyChanged("Id");
}
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
NotifyPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
使用升级后的Student对象作为绑定源的话,当Student对象的Id属性或者Name属性发生变化时,便会触发PropertyChanged事件,Binding对象接收到这个事件消息后,会得知名为Id或者Name的属性的值发生改变,便会通知Binding的目标对象更新变化后的值。为了验证这点,我们为显示学生姓名的TextBlock控件增加MouseEnter事件的处理方法,在该方法中修改Student对象Name属性的值,代码如下所示:
private void TextBlock_StudentName_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
Student.Name = "sanwuyueer";
}
再次运行程序,将鼠标移至TextBlock_StudentName控件,得到以下效果图:
图3 使用升级后的Student对象进行绑定的效果
学生姓名由“三五月儿”变成了“sanwuyueer”,Name属性的变更是不是更新至画面啦。
8 为绑定对象增加值校验器和值转换器
其实,还可以为Binding对象指定值转换器,使用值转换器可以将值从一种类型转换成另外一种类型,关于值转换器,请阅读文章《WPF值转换器 》;还可以为绑定指定值校验器,通过值校验器可以校验数据的正确性,关于值校验器请阅读文章《WPF值转换器 》。至此,Binding模型中又增加了更多功能,也变得更加强大,升级后的Binding模型如下图所示。
图4 升级后的Binding模型示意图
9 在XAML中实现绑定
除了可以在后端代码中完成绑定操作,还可以在XAML代码中也可以完成这个操作。
为了在XAML中实现绑定,需要修改XAML代码,修改后的代码如下所示:
<Window x:Class="WpfBindingExp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="clr-namespace:WpfBindingExp"
Title="MainWindow" Height="350" Width="525">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="4*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Grid.Row="2" Orientation="Horizontal" Background="Aqua">
<StackPanel.DataContext>
<local:Student Id="10000" Name="三五月儿"/>
</StackPanel.DataContext>
<TextBlock Text="学号:" Height="30" Margin="10,0,0,0"/>
<TextBlock x:Name="TextBlock_StudentId" Text="{Binding Path=Id,Mode=TwoWay}" Width="100" Height="30" Margin="0,0,50,0"/>
<TextBlock Text="姓名:" Height="30"/>
<TextBlock x:Name="TextBlock_StudentName" Text="{Binding Path=Name,Mode=TwoWay}" Width="100" Height="30" MouseEnter="TextBlock_StudentName_MouseEnter" />
</StackPanel>
</Grid>
</Window>
代码中使用xmlns:local ="clr-namespace:WpfBindingExp"引入名称空间WpfBindingExp,因为需要使用的Student类的定义位于该名称空间中。设置StackPanel控件的DataContext属性值为Student对象,这样一来,DataContext属性的值将作为StackPanel容器中所有控件绑定的数据源,而内部控件在进行绑定操作时就不再需要设置绑定的Source属性了,只需要设置绑定的Path属性和Mode属性即可。
下面将对DtataContext的工作原理进行说明。
10 使用DataContext属性
前面的实例中,在构造Binding对象时,需要使用Source属性指定绑定的源,其实也可以使用WPF控件的DataContext属性来指定绑定的源。DataContext属性被定义在FrameworkElement类里,这个类是所有WPF控件的基类,所以,WPF中所有控件都将具有该属性。这样一来,在WPF控件树(画面中所有控件构成了树形结构,简称为控件树)的每一个节点上都将具有该属性,所以,我们可以通过在控件树的任意节点设置其DataContext属性来指定绑定的源,而初始化绑定对象时就不再需要指定其Source属性了,只需要设置其Path属性即可。当一个Binding对象只知道自己的Path而不知道Source时,它就会沿着控件树一路向树的根部找去,每路过一个节点就看看该节点的DataContext中是否具有Path指定的属性,若有就将该对象作为绑定的Source,若没有,就继续找下去,直到找到为止,如果到了树根都还没有找到满足条件的Source,那就得不到需要的数据了。所以,在本文的实例中,也可以通过设置Grid或者Window的DataContext属性来指定绑定的数据源,同样可以达到要求。
<Grid.DataContext>
<local:Student Id="10000" Name="三五月儿"/>
</Grid.DataContext>
在实际开发时,往往会将用于绑定的所有对象封装进一个类,再将该类的实例指定为Window的DataContext属性,这样一来,画面中所有控件的绑定源均可以在Window的DataContext中找到,下面所给的实例就是这么干的。
四 再来一个实例
请忘掉前面的代码吧,下面给出一个新实例,也可以说是一个升级版的实例。
下面是完整的代码。关于该实例不作任何说明,大家就自己去研究吧。
1 XAML代码
<Window x:Class="WpfBindingExp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="clr-namespace:WpfBindingExp"
Title="MainWindow" Height="350" Width="525">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="2*"></RowDefinition>
<RowDefinition Height="4*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="TextBlock_Title" Text="{Binding Path=Title}" FontSize="32" Grid.Row="1" Width="100" Height="50"/>
<StackPanel Grid.Row="2" Orientation="Horizontal" Background="Aqua" DataContext="{Binding Path=Student}">
<TextBlock Text="学号:" Height="30" Margin="10,0,0,0"/>
<TextBlock x:Name="TextBlock_StudentId" Text="{Binding Path=Id,Mode=TwoWay}" Width="100" Height="30" Margin="0,0,50,0"/>
<TextBlock Text="姓名:" Height="30"/>
<TextBlock x:Name="TextBlock_StudentName" Text="{Binding Path=Name,Mode=TwoWay}" Width="100" Height="30" MouseEnter="TextBlock_StudentName_MouseEnter" />
</StackPanel>
<ListBox Grid.Row="3" x:Name="ListBox_CourseList" ItemsSource="{Binding Path=CourseList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="TextBlock_CourseId" Text="{Binding Path=Id}" Width="100" Height="30" Margin="10,0,10,0"/>
<TextBlock x:Name="TextBlock_CourseName" Text="{Binding Path=Name}" Width="100" Height="30" Margin="10,0,10,0"/>
<TextBlock x:Name="TextBlock_CourseTeacher" Text="{Binding Path=Teacher}" Width="100" Height="30" Margin="10,0,10,0"/>
<TextBlock x:Name="TextBlock_CourseScore" Text="{Binding Path=Score}" Width="100" Height="30" Margin="10,0,10,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
2 CS代码
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.ComponentModel;
using System.Collections.Generic;
namespace WpfBindingExp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new BindingExpViewModel();
}
}
public class Student:INotifyPropertyChanged
{
private int id;
public int Id
{
get
{
return id;
}
set
{
id = value;
NotifyPropertyChanged("Id");
}
}
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
NotifyPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public string Teacher { get; set; }
public int Score { get; set; }
}
public class BindingExpViewModel
{
public string Title { get; set; }
public Student Student { get; set; }
public List<Course> CourseList { get; set; }
public BindingExpViewModel()
{
Title = "公告";
Student = new Student() { Id = 10000, Name = "三五月儿" };
CourseList = new List<Course>()
{
new Course(){Id = 223,Name="操作系统",Teacher="张三",Score=85},
new Course(){Id = 224,Name="数据库",Teacher="李月",Score=81},
new Course(){Id = 223,Name="数据结构",Teacher="乌拉",Score=80}
};
}
}
}
3 运行程序后的效果图。
图5 升级版示例程序的运行效果图
五 总结
本文通过实例介绍了WPF绑定中最基础的一些知识点,有关绑定更多知识的介绍会在以后的文章中慢慢来说。