将元素绑定到一起

  数据绑定的最简单情形是,源对象是WPF元素而且源属性是依赖属性。

绑定表达式

  数据绑定表达式使用XMAL标记扩展(因此具有花括号)。因为正在创建System.Windows.DataBinding类的一个实例,所以绑定表达式以单词Binding开头。尽管可采用多种方式破诶只Binding对象,但本示例只需要设置两个属性:ElementName属性(指示源元素)和Path属性(指示源元素中的属性)

1
2
3
4
<StackPanel>
<Slider x:Name="silder" Minimum="10" Maximum="100"/>
<TextBlock FontSize="{Binding ElementName=silder,Path=Value}" Text="Simple Text"/>
</StackPanel>

之所以使用名称Path而不是Property,是因为Path可能指向属性的属性(如FontFamily.Source),也可能只想属性使用的索引器(如Content.Children[0])。可构建具有多级层次的路径,使其指向属性的属性的属性,依此类推。

绑定错误

  WPF 不会引发异常来通知与数据绑定相关的问题。如果指定的元素或属性不存在,那么不会收到任何指示;相反,只是不能在目标属性中显示数据。
  乍一看,对调试而言这像是可怕的梦魇。幸运的是,WPF输出了绑定失败细节的跟踪信息。当调试应用程序时,该信息显示在 VisuaStudio 的 Output 窗口中。

绑定模式

  数据绑定的一个特性是目标会被自动更新,而不考虑源的修改方式。

BindingMode枚举值
名称 说明
OneWay 当源属性变化时更新目标属性
TwoWay 当源属性变化时更新目标属性,并且当目标属性变化时更新源属性
OneTime 最初根据源属性值设置目标属性。然而,其后的所有改变都会被忽略(除非绑定被设置为一个完全不同的对象或者调用 BindingExpression.UpdateTarget()方法,正如稍后介绍的那样)。通常,如果知道源属性不会变化,可使用这种模式降低开销
OneWayToSource 与OnWay 类型类似,但方向相反。当目标属性变化时更新源属性(这看起来有点像向后传递),但目标属性永远不会被更新
Default 此类绑定依赖于目标属性。既可以是双向的(对于用户可以设置的属性,如TextBox.Text属性),也可以是单向的(对于所有其他属性)。除非明确指定了另一种模式,否则所有绑定都使用该方法

使用代码创建绑定

  在构建窗口时,在XAML标记中使用Binding标记扩展来声明绑定表达式通常最高效。然而,也可使用代码创建绑定

1
2
3
4
5
6
7
8
public MainWindow()
{
Binding binding = new Binding();
binding.Source = silder; // 源对象
binding.Path = new PropertyPath("Value"); // 源属性
binding.Mode = BindingMode.OneWay; // 绑定模式
simpleText.SetBinding(TextBlock.FontSizeProperty, binding);
}
1
2
3
4
<StackPanel>
<Slider x:Name="silder" Minimum="10" Maximum="100"/>
<TextBlock x:Name="simpleText" Text="Simple Text"/>
</StackPanel>

使用代码检索绑定

  可使用代码检索绑定并检查其属性,而不必考虑绑定最初是用代码还是标记创建的。

通过GetBinding
1
Binding binding = BindingOperations.GetBinding(simpleText,TextBlock.FontSizeProperty);
通过GetBindingExpression
1
2
3
4
5
6
7
BindingExpression expression = BindingOperations.GetBindingExpression(simpleText,TextBlock.FontSizeProperty);

// Get the source element
SIlder boundObject = (Slider)expression.ResolvedSource;

// Get any data you need from the source element, including its bound property.
string boundData = boundObject.FontSize;

多绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<StackPanel>
<Slider x:Name="silder"
Minimum="10"
Maximum="100" />
<ListBox x:Name="lbColors">
<SolidColorBrush Color="Blue" />
<SolidColorBrush Color="Red" />
<SolidColorBrush Color="Yellow" />
</ListBox>
<TextBox x:Name="textBox" />
<TextBlock x:Name="simpleText"
Text="{Binding ElementName=textBox,Path=Text,Mode=OneWay}"
Foreground="{Binding ElementName=lbColors,Path=SelectedValue}" />
</StackPanel>

绑定更新

  绑定更新的触发时机由Binding.UpdateSourceTrigger属性控制。

UpdateSourceTrigger枚举值
名称 说明
PropertyChanged 当目标属性发生变化时立即更新源
LostFocus 当目标属性发生变化并且目标失去焦点时更新源
Explicit 除非调用BindingExpression.UpdateSource()方法,否则无法更新源
Default 根据目标属性的元数据确定更新行为(从技术角度看,是根据FrameworkPropertyMetadataDefaultUpdateSourceTriggcr属性决定更新行为)。大多数属性的默认行为是PropertyChanged但 TextBox.Text属性的默认行为是 LostFocus
1
2
<TextBox x:Name="textBox"
Text="{Binding ElementName=simpleText,Path=FontSize,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />

TextBox.Text属性的默认行为是LostFocus,这仅是因为当用户输入内容时,文本框中的文本会不断地变化,从而会引起多次更新。根据源控件更新自身的方式,PropertyChanged 更新模式会使应用程序的运行更缓慢。此外,可能会导致源对象在编辑完成之前重新更新自身,而这可能引起验证问题。

延迟绑定

  在极少数情况下,需要防止数据绑定触发操作和修改源对象,至少在某一时段是这样的。例如,可能想在从文本框复制信息之前暂停,而不是在每次按键后获取。或者,源对象在数据绑定属性变化时执行处理器密集型操作。在此情况下,可能要添加短暂的延迟时间,避免过分频繁地触发操作。
  在这些特殊情况下,可使用 Binding对象的 Delay 属性。等待数毫秒,之后再提交更改。下面是文本框示例的修改版本,会在用户停止输入500 毫秒(半秒钟)后更新源对象:

1
2
<TextBox x:Name="textBox"
Text="{Binding ElementName=simpleText,Path=FontSize,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,Delay=500}" />

绑定到非元素对象

  当绑定到非元素对象时,需要放弃Binding ElementName属性,并使用以下属性中的一个:

  • Source :甘肃新时指向源对象的引用——换句话说,时提供数据的对象
  • RelativeSource :这是引用,使用RelateveSource对象指向源对象。有了这个附加层,可在当前元素(包含绑定表达式的元素)的基础上构建引用。这似乎无谓地增加了复杂程度,但实际上,RelativeSource属性是一种特殊工具,当编写控件模板以及数据模板时是很方便的。
  • DataContext:如果没有使用Source或RelativeSource属性指定源,WPF 就从当前元素开始在元素树中向上查找。检查每个元素的DataContext属性,并使用第一个非空的DataContext属性。当我要将同一个对象的多个属性绑定到不同的元素时,DataContext属性是非常有用的,因为可在更高层次的容器对象上(而不是直接在目标元素上)设置DataContext 属性。

Source属性

  最简单的选择时将SOurce属性指向一些已经准备好了的静态对象。

1
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily},Path=Source}"/>

  另一种选择是绑定到先前作为资源创建的对象。例如,下面的标记创建指向Calibri字体的FontFamily 对象:

1
2
3
4
5
6
<Window.Resources>
<FontFamily x:Key="CustomFont">Calibri</FontFamily>
</Window.Resources>
<StackPanel>
<TextBlock Text="{Binding Source={StaticResource CustomFont},Path=Source}"/>
</StackPanel>

RelativeSource属性

  通过 RelativeSource 属性可根据相对于目标对象的关系指向源对象。例如,可使用RelativeSource 属性将元素绑定到自身或其父元素(不知道在元素树中从当前元素到绑定的父元素之间有多少代)。

  为设置 Binding.RelativeSource 属性,需要使用 RelativeSource 对象。这会使语法变得更加复杂,因为除了需要创建 Binding对象外,还需要在其中创建嵌套的 RelativeSource 对象。一种选择是使用属性设置语法而不是使用 Binding 标记扩展。例如,下面的代码为 TextBlock.Text属性创建了一个 Binding 对象,这个 Binding 对象使用査找父窗口并显示窗口标题的 RelativeSource对象:

1
2
3
4
5
6
7
8
9
10
<TextBlock>
<TextBlock.Text>
<Binding Path="Title">
<Binding.RelativeSource>
<RelativeSource Mode="FindAncestor"
AncestorType="{x:Type Window}" />
</Binding.RelativeSource>
</Binding>
</TextBlock.Text>
</TextBlock>

编写绑定更常用的方法是使用 Binding和 RelativeSource 标记扩展,将其合并到一个字符串:

1
<TextBlock Text="{Binding Path=Title, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}" />
RelativeSourceMode枚举值
名称 说明
Self 表达式绑定到同一个元素的另一个属性上
FindAncestor 表达式绑定到父元素。WPF 将查找元素树直至发现期望的父元素。为了指定父元素,还必须设置 AncestorType 属性以指示希望査找的父元素的类型。此外,还可以用AncestorLevel属性略过发现的一定数量的特定元素。例如,当在一棵树中查找时,如果希望绑定到第三个ListBoxltem 类型的元素,应当使用如下设置–AncestorType-{x:Type ListBoxltem};并且 AncestorLevel=3,从而略过前两个ListBoxItem元索。默认情况下,AncestorLevel 属性设置为1,并在找到第一个匹配的元素时停止查找
PreviousData 表达式绑定到数据绑定列表中的前一个数据项。在列表项中会使用这种模式
TeampleParent 表达式绑定到应用模板的元素。只有当绑定位于控件模板或数据模板内部时,这种模式才能工作

DataContext属性

  在某些情况下,会将大量元素绑定到同一个对象。例如,下面的一组TextBlock元素,需要为每一个元素绑定表达式显得很臃肿,因此可以给父元素绑定DataContext属性

1
2
3
4
5
6
7
<StackPanel>
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily},Path=Source}" />
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily},Path=LineSpacing}" />
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily},Path=FamilyTypefaces[0].Style}" />
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily},Path=FamilyTypefaces[0].Weight}"/>
</StackPanel>

1
2
3
4
5
6
<StackPanel DataContext="{x:Static SystemFonts.IconFontFamily}">
<TextBlock Text="{Binding Path=Source}" />
<TextBlock Text="{Binding Path=LineSpacing}" />
<TextBlock Text="{Binding Path=FamilyTypefaces[0].Style}" />
<TextBlock Text="{Binding Path=FamilyTypefaces[0].Weight}"/>
</StackPanel>

如果使用Source属性创建明确标识源的绑定,元素就会使用源而不会使用可能得到的任何数据上下文