WPF 入门《数据绑定》
简单而言, 数据绑定是一种关系, 这种关系告诉WPF 从一个源目标对象中提取一些信息, 并且使用该信息设置为目标对象的属性。目标属性总是依赖项属性, 并且通常位于WPF元素中。
然而, 源对象可以是任何内容, 可是是随机生成的一个对象、也可以是数据库的数据对象,或者手动创建的对象。
1.简单绑定
为了能够简单理解这种绑定关系, 接下来简单示例, 用一个数值滚动条, 动态修改一个文字的字体大小, 通过绑定的方式。
<StackPanel> <Slider Name="s1" Value="10" Maximum="100"></Slider> <TextBlock FontSize="{Binding ElementName=s1,Path=Value}" Text="看着我" ></TextBlock> </StackPanel>
上图中, TextBlock的字体大小FontSize通过绑定的形式与名称为s1的元素的Value属性绑定。
注:数据绑定表达式使用了XAML标记扩展, 所以绑定表达式以单词Binding 开头。该示例中设置的两个属性: ElementName(源对象元素) 和Path(源对象元素中的属性)。
运行测试效果:
绑定错误
WPF不会引发异常来通知与数据绑定相关的问题, 如果指定的元素不存在或错误, 不会收到任何提示, 最简单的只是目标的属性无法发生改变, 不过, WPF仍然会在输出窗口显示其绑定失败的具体细节。
绑定模式
在上面的示例中, 只是一个最简单的绑定, 我们可以假设一个环境, 在上面的例子基础上另外加一个按钮, 去单独实现字体的大小设置, 然而拖动条并不会随着按钮设置的字体大小而改动。
但是在实际应用中, 我们甚至会用到的多种绑定模式, 所以在WPF中, 含有5种绑定的模式, 包含在System.Windows.Data.BindingMode枚举中。
OneWay | 当源属性变化时更新目标属性 |
TwoWay | 当源数据变化时更新目标属性, 并且当目标属性变化时更新源属性 |
OneTime | 最初根据源属性值设置目标属性。然而, 在此之后的所有改变都会被忽略(除非绑定被设定为一个完全不同的对象或者调用BindingExpression.UpdateTarget()方法, 如稍后所介绍的那样)。通常, 如果知道源属性不会变化, 可以使用这种模式降低开销。 |
OneWayToSource | 和OneWay 类似, 但是方向相反。当目标属性变化时更新源属性(这看起来有点像向后传递),但目标属性永远不会更新。 |
Default | 此类绑定依赖于目标属性。它既可以是双向的(对于用户设置的属性, 如Textbox.Text属性),也可以是单向的(对于所有其他属性)。除非明确指定了另外一种模式, 否则所有绑定都使用该方法。 |
在此, 对于OneTime设置过一次就无效,和默认default模式就不过多介绍, 下面对其他集中进行简单分析:
利用代码进行数据绑定
下面代码演示如何将上面的示例进行绑定:
private void Window_Loaded(object sender, RoutedEventArgs e) { Binding bind = new Binding(); bind.Source = s1; //指定源对象 bind.Mode = BindingMode.Default; //设置绑定模式 bind.Path = new PropertyPath("Value"); //关联绑定的属性 txtfont.SetBinding(TextBlock.FontSizeProperty, bind); //绑定对象 }
下面代码演示如何接触数据绑定:
private void Window_Loaded(object sender, RoutedEventArgs e) { //删除指定目标对象的 指定属性数据绑定 BindingOperations.ClearBinding(txtfont, TextBlock.FontSizeProperty); //删除指定目标对象的所有数据绑定 BindingOperations.ClearAllBindings(txtfont); }
注意:以上的两个移除数据绑定的方法, 可以删除任何形式的绑定, 无论是代码创建的绑定还是通过XAML标记的绑定。
绑定更新:
在上面的示例中, 从 源对象 -> 目标对象, 源对象的值发生改变, 目标会立刻响应。但是从目标 > 源 , 未必会立即发生。
在WPF中,他们的行为右 binding中的 UpdateSourceTrigger属性控制, 关于UpdateSourceTrigger 下面列出了对应的枚举值
propertyChanged | 当目标属性发生变化时立即更新源目标 |
LostFocus | 当目标属性发生变化时并且目标丢失焦点是更新源目标 |
Explicit | 除非调用BindingExpression.UpdateSource()方法,否则无法更新资源 |
Default | 根据目标属性的元数据确定更新行为, 大多数属性的默认行为是PropertyChanged, 但是TextBox.Text的属性默认行为是LoastFocus |
示例, 对于TextBox控件, 添加 UpdateSourceTrigger=PropertyChanged 以达到更新源目标, 但是往往在实际应用中, TextBox会频繁的变化, 从而导致多次更新源目标,
PropertyChenged更新反而会是应用程序运行更加缓慢, 要完全控制源对象的更新时机, 可以选择 Explicit模式, 例如, 添加一个按钮, 在按钮中调用UpdateSource方法已达到
更新的目的。
<TextBlock Grid.Row="1" Text="看着我" Name="txtfont"></TextBlock> <TextBox Grid.Row="2" Margin="10" Text="{Binding ElementName=txtfont,Path=FontSize,UpdateSourceTrigger=PropertyChanged}"></TextBox>
注:在调用UpdateSource之前, 需要通过GetBindingExpression()方法获取到BindingExpression对象, 从而调用UpdateSource()方法。
private void Button_Click(object sender, RoutedEventArgs e) { BindingExpression exp = txtfont.GetBindingExpression(TextBlock.FontSizeProperty); exp.UpdateSource(); }
非元素绑定:
在上面的例子中, 一直围绕着元素之间的绑定, 以及一些绑定的模式的介绍, 但是在我们的实际应用当中, 更多的是于数据打交道, 就像常见的Grid表格, 需要绑定对应数据库表的每个字段。
当绑定一个非元素对象时候, 这个时候就不能使用 ElementName属性, 在WPF中, 提供了多种属性以在实际应用中选择:
Source | 提供数据的对象本身 |
RelativeSource | 使用RelativeSource对象指向源目标, |
DataContext | 从当前元素向下, 找到第一个非空的DataContext属性。 |
Source属性:
如下, 我们定义了一个Custom的资源对象, 同时创建了一个TextBlock对象用Source进行了对象绑定, 此时, TextBlock显示的Text则为Tom
<Window.Resources> <FontFamily x:Key="Custom">Tom</FontFamily> </Window.Resources> <StackPanel> <TextBlock Text="{Binding Source={StaticResource Custom},Path=Source}" Height="30"/> </StackPanel>
RelativeSource属性:
如下, 定义了一个RelativeSource对象, Mdoe选择了FindAncestor(该模式通知元素直到发现AncestorType定义的元素类型,如下类型为 window),选择的Paht为title属性
<StackPanel> <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},Path=Title}" Height="30"/> </StackPanel>
DataContext属性:
TextBlock绑定了DataContext属性, WPF会在元素树种,找到第一个不为null的数据上下文, 此时后台需要对DataContext进行绑定指定的上下文对象。
<TextBlock Text="{Binding Width}"/>
该示例种, 绑定了自身为上下文对象, 所有TextBlock找到的第一个Width属性为windows窗体本身的Width属性
private void Window_Loaded(object sender, RoutedEventArgs e) { this.DataContext = this; }