备注:类关系结构图如下:
Binding
Binding基础
-
Binding作用:目的是实时变化,Binding的源是逻辑层的对象,Binding目标是UI层控件对象。
-
一个例子:
class Student: INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string name; public string Name { get{return name;} set { name = value; // 激发事件 this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name")) } } } // xaml中 <TextBox x:Name="textBoxName" BorderBrush="Black" Margin="5" /> <Button Content="Add Age" Margin="5" Click="Button_Click" /> // xaml对应的cs中 Student stu; public Window1() { InitialComponent(); // 准备数据源 stu = new Student(); // 准备Binding Binding binding = new Binding(); binding.Source = stu; binding.Path = new PropertyPath("Name"); // 使用Binding链接数据源和Binding目标 BindingOperations.SetBinding(this.textBoxName, TextBox.TextProperty, binding); // 备注:上面这些也可以合并成为一句 this.textBoxName.SetBinding(TextBox.TextProperty, new Binding("Name"){Source = stu = new Student()}); } private void Button_Click(object sender, RoutedEventArgs e) { stu.Name += "Name"; }
效果展示:
-
模型:
Binding的源和路径
备注:上面写的Student就可以作为源,下面讨论不同情况下的源。
1. 把控件作为Binding源与Binding标记扩展
备注:
- 在C#代码中可以访问XAML代码中声明的变量但XAML代码中无法访问C#代码中声明的变量,因此,想要在XAML中建立UI元素和逻辑层对象的Binding还要颇费周折,把逻辑层对象声明为XAML代码中资源,然后使用资源。
- 上面也可简写成如下,参数->构造函数:
<TextBox x:Name="textBox1" Text="{Binding Value, ElementName=slider1}" BorderBrush="Black" Margin="5" />
- 与之等价的C#代码是
this.textBox1.SetBinding(TextBox.TextProperty, new Binding("Value"){ElementName="slider1"});
2. 控制Binding的方向及数据更新
- 控制Binding数据流向的属性是Mode,他的类型是BindingMode枚举。BindingMode可取值为TwoWay、OneWay、OneWayToSource和Default。这里Default值是指Binding的模式会根据目标的实际情况来确定,比如若是可编辑的(如TextBox.Text属性),Default就采用双向模式;若是只读的(如TextBlock.Text)则采用单向模式。
- Binding的另一个属性—UpdateSourceTrigger,他的类型是UpdateSourceTrigger枚举,可取值为PropertyChanged、LostFocus、Explicit和Default。
3. Binding的路径(Path)
- 一般情况下:
<TextBox x:Name="textBox1" Text="{Binding Path=Value, ElementName=slider}">
等效的C#代码是:Binding binding = new Binding(){Path=new PropertyPath("Value"), Source=this.slider}; this.textBox1.SetBinding(TextBox.TextProperty, binding); 或者: this.textBox1.SetBinding(TextBox.TextProperty, new Binding("Value"){Source=this.slider});
- 文本长度
Text.Length 替代上面的 Value 备注:同时 Mode="OneWay" 和 Source位置一样,作为字段名赋值
- 集合类型的索引器又称为带参属性,既然是属性,索引器也能作为Path来使用。
Text.[3] 或者 Text[3]
4. 没有path的binding
备注:
- Binding源本身就是数据且不需要path来指定,典型的string、int等基本类型就是这样。
- 所以在xaml代码中可以写".“也可以不写,但是在C#代码中”."不能省略。
- 上面代码在C#代码中如下:
// xaml中不写如下 Text = "{Binding Source={StaticResource ResourceKey = myString}}" // C#如下 string myString = "菩提本无树,明镜亦非台。本来无一物,何处染尘埃。" this.textBlock.SetBinding(TextBlock.TextProperty, new Binding("."){Source= myString});
5. 没有Source的Binding–使用DataContext作为Binding源
-
DataContext属性被定义在FrameworkElement类里,这个类是WPF控件的基类,所以UI元素树的每个节点都有DataContext。
-
当一个Binding只知道自己的Path而不知道自己的Source时,他会沿着UI元素树一路向书的根部找过去。
-
如果找到根部还没有找到Path对应的Source,那这个Binding就没有Source,因而也不会得到数据。
-
示例代码:
public class Student { public int Id{get; set;} public string Name{get; set;} public int Age{get; set;} }
-
当Binding的Source本身就是数据,不需要使用属性来暴露数据,Binding的Path可以设置为“.”,也可以省略不写,同时Source也可以省略不写,所以有如下:
<StackPanel> <TextBlock Text="{Binding}" Margin="5" /> <TextBlock Text="{Binding}" Margin="5" /> <TextBlock Text="{Binding}" Margin="5" /> </StackPanel>
6. 使用集合对象作为列表控件的 ItemSource
备注:
- WPF中列表控件们派生自 ItemsControl 类,自然也就继承了 ItemsSource 属性,ItemsSource 属性可以接受一个 IEnumerable 接口派生类作为自己的值(所有可以被迭代遍历的集合都实现了这个接口)。
代码例子:
7. 使用 DataTable 数据作为 Binding 的源
代码例子:
- ListBox 中 DataTable 作为源
备注:this.listBoxStudents.ItemsSource = dt.DefaultView;
,其中的 DataTable 的 DefaultView 属性是一个 DataView 类型的对象,DataView 类实现了 IEnumerable 接口,所以可以被赋值给 ListBox.ItemsSource 属性。 - ListView 控件中 DataTable 作为源(自定义格式)。
备注:DataTable 不能直接拿来作为 ItemsSource 赋值,不过,当你把 DataTable 对象放在一个对象的 DataContext 属性里,是可以的,如下:
private void Button_Click(object sender, RoutedEventArgs e)
{
DataTable dt = this.Load();
this.listViewStudents.DataContext = dt;
this.listViewStudents.SetBinding(ListView.ItemsSourceProperty, new Binding());
}
8. 使用 XML 数据作为 Binding 的源
- 代码例子1
备注:
- 需要注意的是,当使用 XML 数据作为 Binding 的 suorce 时,我们将使用 XPath 属性,而不是 Path 属性来指定数据的来源。
- 使用 @ 符号加字符串表示 XML 元素的 Attribute,不加 @ 符号的字符串表示子级元素。
2. 代码例子2
备注:需要注意的是,如果把 XmlDataProvider 直接放在 XML 代码里,那么它的 XML 数据需要放在 <x:XData> ... </x:XData>
标签里。
8. 使用 LINQ 数据作为 Binding 的源
- LINQ 查询的结果是一个 IEnumerable 类型的对象,而 IEnumerable 又派生自 IEnumerable,所以它可以作为列表控件的 ItemsSource 来使用。
- 不同情况下写法。
- 直接泛型
- 如果数据存放在一个已经填充好的 DataTable 对象里
- 如果数据存放在 XML 文件里,如下
- 直接泛型
9. 使用 Binding 的 RelativeSource
- 有明确数据源的时候我们可以通过 Source 和 ElementName 赋值的方法让 Binding 与之关联,有些时候不确定对象叫什么名字,只知道他们 UI 之间的依赖关系,可以使用 RelativeSource 属性。
- 代码例子:
- 寻找祖先
- 寻找自己
- 备注:RelativeSource 类中的 Mode 属性的类型是 RelativeSourceMode 枚举,他的取值有:
PreviousData、TemplatedParent、Self、FindAncestro
- 寻找祖先
Binding 的数据校验和转换
Binding 的数据校验
下面程序是 UI 上有一个 TextBox 和 Slider,然后后台 Binding 关联起来,Slider 是源,TextBox 为目标,Slider 取值范围是0-100,需要校验的是 TextBox 里输入的值是不是0-100范围内。
备注:
- TextBox 中输入0-100程序显示正常,但是输入这个区间之外的值或不能被解析的值时,TextBox 会显示红色边框,表示值时错误的,不能传递给 Source。
- Binding 只在 Target 被外部方法更新时才校验数据,而来自 Binding 的 Source 数据更新 Target 是不会校验的,如果想改变这种行为,可以将校验条件的 ValidatesOnTargetUpdated 属性设为 true。
- 默认校验错误之后,错误信息是不显示的,如果想显示,需要将 Binding 对象的 NotifyOnValidationError 属性设置 true,这样 error 信号才会在 UI 元素树上往上传递。
Binding 的数据转换
- 引入
- 作用于 Source 和 Target 之间,其实现的接口,Convert 是正方向,ConvertBack 是反方向,就是从 Target 到 Source 需要经过的转换。Binding 对象的 Mode 属性会影响到两个方法的调用,如果是 TwoWay 两个方法都有可能被调用,如果是 OneWay 则只有一个 Convert 被调用。
- 简单的转换WPF已经帮我们做了,有些复杂的需要我们自己写。
- 代码例子
备注:Convert 参数:第一参数 object 可以在方法体内对实际类型进行判断。第二个参数用于确定方法的返回类型。第三个参数用于把额外的信息传入方法,若需要传递多个信息可以把信息彷如一个集合对象来传入方法。
MultiBinding (多路 Binding)
-
引入:有时候 UI 需要显示的信息由不止一个数据来源决定,这时候就需要使用 MultiBinding。凡是能使用 Binding 对象的场合都能使用 MultiBinding,其类型是 Collection,通过这个属性把一组 Binding 对象聚合起来,出在这个集合中的 Binding 对象可以拥有自己的数据校验和转换机制。
-
代码例子
- 需求:考虑这样一个需求,有一个用于新用户注册的UI(包含4个 TextBox 和一个 Button),还有如下一些限定:
- 第一、二个 TextBox 输入用户名,需求内容一致。
- 第三、四个 TextBox 输入用户 E-Mail,要求内容一致。
- 当 TextBox 的内容全部符合要求的时候,Button 可用。
- 备注:
- MultiBinding 对于添加子级 Binding 的顺序是敏感的,因为这个顺序决定了汇集到 Converter 里数据的顺序。
- MultiBinding 的 Converter 实现的是 INultiValueConverter 接口。
- 需求:考虑这样一个需求,有一个用于新用户注册的UI(包含4个 TextBox 和一个 Button),还有如下一些限定: