简单的说,数据绑定是一种关系,该关系告诉WPF从一个源对象提取一些信息,并使用这些信息设置目标对象的属性。目标属性总是以来属性,并且通常位于WPF元素中,毕竟,WPF数据绑定的最终目的是在用户界面中显示一些信息。然而,源对象可以是任何内容,从另一个WPF元素到ADO.NET数据对象(如DataTable和DataRow对象)或自己创建的纯数据对象。本次先简单介绍数据绑定,一号学习从数据库向数据窗体传递数据的最高效的方式。
8.1 将元素绑定到一起
数据绑定最简单的情况是,源对象是WPF元素并且源属性是依赖项属性。这是因为依赖项属性具有内置的更改通知支持。为了理解如何能够将一个元素绑定到另一个元素,给出如下示例,该示例展示一个用Slider控件控制TextBlock控件字体的示例:
XAML部分
<StackPanel Margin="5"> <Slider Name="sliderFontSize" Margin="3" Minimum="1" Maximum="40" Value="10" TickFrequency="1" IsSnapToTickEnabled="True" TickPlacement="TopLeft"></Slider> <TextBlock Margin="10" Name="lblSampleText" FontSize="{Binding ElementName=sliderFontSize, Path=Value, Mode=TwoWay}" Text="Simple Text"> </TextBlock> <StackPanel Orientation="Horizontal" > <Button Margin="5" Padding="3" Click="cmd_SetSmall">Set to Small</Button> <Button Margin="5" Padding="3" Click="cmd_SetNormal">Set to Normal</Button> <Button Margin="5" Padding="3" Click="cmd_SetLarge">Set to Large</Button> </StackPanel> <StackPanel Orientation="Horizontal" Margin="5"> <TextBlock VerticalAlignment="Center" xml:space="preserve">Exact Size: </TextBlock> <TextBox Text="{Binding ElementName=lblSampleText, Path=FontSize, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Width="100"></TextBox> </StackPanel> </StackPanel>
代码部分
private void cmd_SetSmall(object sender, RoutedEventArgs e) { sliderFontSize.Value = 2; } private void cmd_SetNormal(object sender, RoutedEventArgs e) { sliderFontSize.Value = this.FontSize; } private void cmd_SetLarge(object sender, RoutedEventArgs e) { // Only works in two-way mode. lblSampleText.FontSize = 30; }
8.1.1 绑定表达式
当使用数据绑定时,不需要对源对象进行任何改变。只需要配置源对象使其属性具有正确的值范围。如上示例中Slider控件
<Slider Name="sliderFontSize" Margin="3" Minimum="1" Maximum="40" Value="10" TickFrequency="1" IsSnapToTickEnabled="True" TickPlacement="TopLeft"></Slider>
绑定是在TextBlock元素中进行定义的,在此没有字面值设置FontSize属性,而是使用了一个绑定表达式,如下
<TextBox Text="{Binding ElementName=lblSampleText, Path=FontSize, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Width="100"></TextBox>
数据绑定表达式使用了XAML标记扩展。因为正在创建System.Windows.Data.Binding类的一个实例,所以绑定表达式以单词Binding开头。尽管可以采用多种方式配置Binding对象,但在以上示例中只需要配置两个属性ElementName属性(指示源元素)和Path属性(指示源元素中的属性)。使用名称Path而不是Property,是因为Path可能指向一个属性的属性也可能指向属性使用的索引器(如Content.Children[0])。可以构建具有多级层次的路径指向一个属性的属性的属性。
8.1.2 绑定错误
WPF不会引发异常带来通知与数据绑定相关的问题。如果指定的元素或属性不存在,不会受到任何指示,反而,只是在目标属性中不能显示数据。WPF输出了绑定失败细节的跟踪信息。当调试应用程序时,该信息显示在Visual Studio的输出窗口中。
8.1.3 绑定模式
数据绑定的一个特性是目标会被自动更新,而不管源是被如何修改的。在上面的示例中,源能通过滑条的滑块进行交互。还有三个按钮,每个按钮都为滑条预先设置的值。
private void cmd_SetSmall(object sender, RoutedEventArgs e) { sliderFontSize.Value = 2; } private void cmd_SetNormal(object sender, RoutedEventArgs e) { sliderFontSize.Value = this.FontSize; } private void cmd_SetLarge(object sender, RoutedEventArgs e) { // Only works in two-way mode. lblSampleText.FontSize = 30; }
上面的代码设置滑条的值,会通过数据绑定强制改变字体尺寸。效果与移动滑条一样。我们也能强制设置TextBlock的字体尺寸,但是这样会破坏掉字体尺寸的绑定,如果强制设置TextBlock字体的尺寸,再移动滑条就不会有任何变化。为此,我们可以设置强制在两个方向传递数值:从源到目标以及从目标到源。用法是设置Binding对象的Mode属性。如上面示例中的Mode=TwoWay。下面是Binding.Mode的所有枚举值。
OneWay | 当源属性变化时更新目标属性 |
TwoWay | 当源属性变化时更新目标属性,并且当目标属性变化时更新源属性 |
OneTime | 最初根据源属性设置目标属性。然而,在此之后的所有改变都会被忽略 |
OneWayToSource | 和OnWay类型类似,但是方向相反,当目标属性变化时更新源属性,但是目标属性永远不会被更新 |
Default | 此类绑定依赖于目标属性,他既可以是双向的,也可以是单向的。除非明确制定了另一种模式,否则所有绑定都是用该方法。 |
8.1.4 使用代码创建绑定
在构建一个窗口时,在XAML标记中是用Binding标记扩展声明绑定表达式更高效。也可以使用代码。下面演示了代码创建绑定
Binding binding=new Binding(); binding.Source=sliderFontSize; binding.Path=new PropertyPath("Value"); binding.Mode=BindingMode.TwoWay; lblSampleText.SetBinding(TextBlock.FontSize,binding);
还可以通过代码使用BindingOperation类的两个静态方法移除绑定。ClearBinding()方法使用一个依赖项属性的引用作为参数,而ClearAllBindings()方法为一个元素删除所有数据绑定。ClearBinding()方法和ClearAllBindings()方法都使用ClearValue()方法,每个元素都从DependencyObject基类继承了ClearValue()方法。ClearValue()方法简单的移除属性的本地值。标记绑定比代码创建绑定更常见,因为基于标记的绑定更清晰并且需要完成的工作更少。但一些特殊情况下,要使用代码创建绑定。
- 创建动态绑定——如果希望根据其他运行时信息修改绑定,或者根据环境创建不同的绑定,这时使用代码创建绑定通常更合理。
- 删除绑定——如果希望删除绑定,从而可以通过普通方式设置属性,需要借助ClearBinding()或ClearAllBindings()方法。(这两种方法可以删除任何绑定,不管是XAML绑定还是代码绑定)
- 创建自定义控件——为了让他人能够更容易的修改构建的自定义控件的外观,需要将特定的细节从标记移到代码中。
8.1.5 绑定更新
在8.1中的示例中如果去掉TextBox标签中的'UpdateSourceTrigger=PropertyChanged',那么改变文本框的输入内容,字体尺寸不会发生变化,直到使焦点转移到另外的控件上才会发生变化。然而滑动条不同,当拖动滑动条时字体会马上改变。当使用OneWay或TwoWay绑定时,改变的值会立即从元传播到目标。对于滑动条,TextBlock元素中有一个单向绑定表达式,所以Slider.Value属性值的变化会立即应用于TextBlock.FontSize属性。即:源的变化立即影响目标。在WPF中双向绑定中反向的变化传递——从目标到源——未必会立即发生。反而,它们的行为由Binding.UpdateSource.Trigger属性控制,该属性可以使用如下值
PropertyChanged | 当目标属性发生变化时立即更新源 |
LostFocus | 当目标属性发生变化并且目标丢失焦点时更新源 |
Explicit | 除非调用BindingExpression.UpdateSource()方法,否则无法更新源 |
Default | 根据目标属性的元数据确定更新行为(从技术角度讲,是根据FrameworkPropertyMetadata.DefaultUpdateSourceTrigger属性决定更新行为)。大多数属性的默认行为是PropertyChanged,但TextBox.Text属性的默认行为是LostFocus |
我们在使用Explicit模式时。如果要使用这种模式,需要自己编写触发和改变的方法,我们可以调用BindingExpression.UpdateSource()方法触发并更新。在调用BindingExpression.UpdateSource()方法之前,需要一种方法获取BindingEXPression对象。BindingExpression对象是一个将比较小的两个内容包装到一起的组装包,这两个内容是:Binding对象(通过BindingExpression.ParentBinding属性提供)和经由原绑定的对象(BindingExpression.DataItem)。此外,BindingExpression对象为触发立即更新绑定的一部分提供了两个方法:UpdateSource()和UpdateTarget()方法。为了获取BindingExpression对象,需要使用GetBindingExpression()方法,并传入具有绑定的目标属性,每个元素都从FrameworkElement基类继承了该方法。如下示例
BindingExpression binding= txtFontSize.GetBindingExpression(TextBox.TextProperty);
binding.UpdateSource();
8.2 绑定到非元素对象
在数据驱动的应用程序中,常见的情况是创建从一个不可见对象中提取数据的绑定表达式。唯一的要求是希望显示的信息必须存储在一个公有属性中。WPF数据绑定基础结构不能获取私有信息或共有字段。当绑定到一个非元素对象时,需要放弃Binding.ElementName属性,使用以下属性中的一个:
- Source——该属性是指向源对象的引用,换句话说,是提供数据的对象
- RelativeSource——该属性使用一个RelativeSource对象指向源对象,通过RelativeSource对象指向源对象,通过RelativeSource对象可以在当前元素的基础上构建源对象引用。当编写控件模板以及数据模板时很方便。
- DataContext——如果没有使用Source或RelativeSource属性指定一个源,WPF就从当前元素开始在元素树中向上查找。检查每个元素的DataContext属性,并使用第一个非空的DataContext属性。如果需要将同一个对象的多个属性绑定到不同的元素,DataContext属性非常有用,因为可以在更高层次的容器对象上设置DataContext属性。
8.2.1 Source属性
Source属性非常简单。为了进行绑定,需要具有数据对象。数据对象可以从资源中提取数据对象,可以通过编写代码生成数据对象,也可以在数据提供程序的帮助下获取数据对象。最简单的选项是将Source属性指向一些已经准备好了的静态对象。例如,可以在代码中创建一个静态对象并使用该对象。或者,可以使用来自.NET类库的组件,如下
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily},Path=Source}"></TextBlock>
这个绑定表达式获取由静态的SystemFonts.IconFontFamily属性提供的FontFamily对象(为了设置Binding.Source属性,需要借助静态标记扩展)。然后将Binding.Path属性设置为FontFamily.Source属性,该属性给出了字体家族的名称。另一种选项是绑定到一个先前作为资源创建的对象。如下,将标记创建一个指向Calibri字体的FontFamily对象
<Window.Resources> <FontFamily x:Key="CustomFont">Calibri</FontFamily> </Window.Resources> <TextBlock Margin="5" Text="{Binding Source={StaticResource CustomFont}, Path=Source}"></TextBlock>
8.2.2 RelativeSource属性
通过RelativeSource属性可以根据相对于目标对象的关系指向源对象。例如,可以使用RelativeSource属性将一个元素绑定到自身或绑定到父元素。为了设置Binding.RelativeSource属性,需要使用RelativeSource对象。这使语法变得更加复杂,因为除了需要创建了一个Binding对象之外,还需要在其中创建一个嵌套的RelativeSource对象。一种选择是使用属性设置语法而不是使用Binding标记扩展。例如,TextBlock.Text属性创建一个Binding对象。这个Binding对象使用了一个查找父窗口并显示窗口标题的RelativeSource对象。
<TextBlock> <TextBlock.Text> <Binding Path="Title"> <Binding.RelativeSource> <RelativeSource Mode="FindAncestor" AncestorType="{x:Type Window}"/> </Binding.RelativeSource> </Binding> </TextBlock.Text> </TextBlock>
RelativeSource对象使用FindAncestor模式,该模式告知查找元素树直到发现AncestorType属性定义的元素类型。编写绑定更常用的方法是使用Binding和RelativeSource标记扩展,将其合并到一个字符串中如下
<TextBlock Text="{Binding Path=Title, RelativeSource={RelativeSource FindAncestor, AncestorType ={x:Type Window}}}"></TextBlock>
下面是创建RelativeSource对象的四种模式
Self | 表达式绑定到同一元素的另一个属性上 |
FindAncestor | 表达式绑定到父元素。WPF将查找元素树直至发现期望的父元素。为了指定父元素,还必须设置AncestorType属性以指示希望查找的父元素的类型。此外,还可以使用AncestorLevel属性滤过发现的一定数量的特定元素。 |
PreviousData | 表达式绑定到数据绑定列表中的前一个数据项。在一个列表项目中会使用这种模式 |
TemplateParent | 表达式绑定到应用模板的元素。只有当绑定位于一个控件模板或数据模板内部时,这种模式才能工作。 |
8.2.3 DataContext属性
在某些情况下,会将大量元素绑定到同一对象。例如,分析如下一组TextBlock元素,每个TextBlock元素都是用类似的绑定表达式提取与默认图标字体相关的不同细节。可以为每个TextBlock元素使用Source属性,但会使标记变得很长
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily},Path=Source}"></TextBlock> <TextBlock Margin="5" Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=LineSpacing}"></TextBlock> <TextBlock Margin="5" Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=FamilyTypefaces[0].Style}"></TextBlock> <TextBlock Margin="5" Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=FamilyTypefaces[0].Weight}"></TextBlock>
对于这种情况,使用FrameworkElement.DataContext属性定义绑定源会更加清晰并且更灵活。在上面的示例中,为包含所有TextBlock元素的StackPanel面板设置DataContext属性是合理的,可以使用和设置Binding.Source属性相同的方法设置元素的DataContext属性。也就是说可以提供内联对象,或从一个静态属性中提取,或者从一个资源中提取。如下
<StackPanel Margin="10" DataContext="{x:Static SystemFonts.IconFontFamily}"> <TextBlock Margin="5" Text="{Binding Path=Source}"></TextBlock> </StackPanel>
当在绑定表达式中省略源信息时,WPF会检查元素的DataContext属性。如果它为null,WPF会继续向上在元素树中查找第一个部位null的数据。如果找到了一个数据对象就为它绑定使用绑定的数据。否则不会为目标水洗那个应用任何值。
本次只是介绍了数据绑定的基础知识,学习了如何从一个元素提取信息,并在另一个元素上显示信息。以后还会进一步的扩展数据绑定技巧,并将普通的文本转换为丰富的格式化数据显示。