【笔记】【WPF编程宝典】 第08章 元素绑定

本文深入探讨了WPF数据绑定的基础知识,包括绑定表达式、绑定模式、多绑定及更新策略等内容,帮助开发者掌握如何在WPF应用程序中高效地利用数据绑定。


  简单地说,数据绑定是一种关系。 从源对象提取一些信息,并用这些信息设置目标对象的属性目标属性始终是依赖项属性,通常位于WPF元素中——毕竟,WPF数据绑定的最终目标是在用户界面中显示一些信息。

8.1 将元素绑定到一起

  数据绑定的最简单情形是,源对象是 WPF元素而且源属性是依赖项属性。由于依赖项属性具有内置的更改通知支持。因此,当在源对象中改变依赖项属性值时,会立即更新目标对象中的绑定属性
  下例的简单窗口,该窗口包含了两个控件:一个Slider控件和一个具有单行文本的TextBox控件。如果向右拖动滑动条上的滑动调上的滑块,文本字体的尺寸会立即随之增加。如果向左拖动滑块,字体支持会缩小。显然,使用代码创建这些行为不是很难。可简单地响应控件的Slider.ValueChanged事件,并将滑动条控件的当前值复制到TextBlock控件来实现这种行为。不过,通过数据绑定这种行为更加简单。

8.1.1 绑定表达式

  当使用数据绑定时,不必对源对象左任何改动。只需要配置源对象使其属性具有正确的值范围,通常进行如下配置:

<Slider Name="SliderFontSize">
    <Slider.Margin>3</Slider.Margin>
    <Slider.Minimum>1</Slider.Minimum>
    <Slider.Maximum>40</Slider.Maximum>
    <Slider.TickFrequency>1</Slider.TickFrequency>
    <Slider.TickPlacement>TopLeft</Slider.TickPlacement>
    <Slider.IsSnapToTickEnabled>True</Slider.IsSnapToTickEnabled>
    <Slider.Value>10</Slider.Value>
</Slider>
<TextBlock Name="LblSampleText">
    <TextBlock.Margin>10</TextBlock.Margin>
    <TextBlock.Text>Simple Text</TextBlock.Text>
    <TextBlock.FontSize>
        <Binding>
            <Binding.ElementName>SliderFontSize</Binding.ElementName>
            <Binding.Path>Value</Binding.Path>
        </Binding>
    </TextBlock.FontSize>
</TextBlock>

  数据绑定表达式使用XAML标记扩展(因此具有花括号)。因为正在创建System.Windows.Data.Binding 类的一个实例,所以绑定表达式以单词Binding开头。
  之所以使用名称Path而不是Property,是因为Path可指向属性的属性,也可指向属性所使用的索引器。可构建多级层次的路径,使其指向属性的属性的属性,以此类推。
  如果希望引用附加属性,那么需要在圆括号中封装属性名称。例如,如果绑定到Grid控件中的某个元素,路径(Grid.Row)将检索放置元素的行号。

8.1.2 绑定错误

  WPF不会引发异常来通知与数据绑定相关的问题。如果指定的元素或属性不存在,那么不会收到任何指示;相反,指示不能在目标属性中显示数据。WPF能够输出绑定失败细节的跟踪信息。当调试应用程序时,该信息显示在VS的Output窗口中。例如,如果试图绑定到不存在的属性,在Output窗口中将看到与下面类似的信息:

System.Windows.Data Error: 40 : BindingExpression path error: 'Value1' property not found on 'object' ''Slider' (Name='SliderFontSize')'. 
BindingExpression:Path=Value1; DataItem='Slider' (Name='SliderFontSize'); target element is 'TextBlock' (Name='LblSampleText'); target property is 'FontSize' (type 'Double')

  当试图读取源属性时,WPF会 忽略 抛出的任何异常,并不加提示地丢弃因源数据无法转换为目标属性的数据类型而引发的异常。然而,当处理这些问题时还有一种选择——可通知WPF改变源元素的外观以指示发生了什么错误。例如,可使用感叹号图标或红色轮廓标识非法输入。

8.1.3 绑定模式

  数据绑定的一个特性时目标会被自动更新,而不考虑源的修改方式。在这个示例中,源只能通过一种方式进行修改——通过用户与滑动条上的滑块进行的交互。当通过代码改变目标属性的值时,源属性并不会改变。反而,因为直接对目标进行了赋值,破坏了原先的绑定。

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

8.1.3.1 OneWayToSource

  OneWay模式于OneWayToSource以相同方式创建单向绑定,唯一的区别是绑定表达式的放置位置。本质上,OneWayToSource模式允许通过在通常被视为绑定源的对象中放置表达式,从而翻转源和目标。
使用这一技巧最常见的原因是要设置非依赖项属性的属性。由于绑定表达式只能用于设置依赖项属性,但通过使用OneWayToSource模式,可以克服这一限制,但前提是 提供数值的属性本身是依赖项属性。

8.1.3.2 Default

  最初,除非明确指定其他模式,否则可能认为所有绑定都是单向的,着看起来像是符合逻辑的。然而,情况并非如此。为了自我验证这一事实,在此考虑能够改变字体尺寸的绑定文本框的示例。即使删除了Mode=TwoWay设置,这个示例也能工作的很好。这是因为WPF使用了一种不同的、默认情况下依赖于所绑定的属性的模式(从技术角度看,在每个依赖项属性中都有一个元数据——FrameworkPropertyMetadata.BindsTwoWayByDefault标志——该标志只是属性是使用单向绑定还是双向绑定)。
  通常默认绑定模式也正是期望的模式。然后,可设想一个示例,该例具有一个只读的不允许用户改变的文本框。对于这种情况,通过将模式设置为单向绑定可稍微降低一些开销。
  作为一条常用的经验法则,明确设置绑定该模式永远不是坏主意。即使在文本框示例中,也值得通过包含Mode属性来强调希望使用的双向绑定。

8.1.4 使用代码创建绑定

Binding binding = new Binding();
binding.Source = SliderFontSize;
binding.Path = new PropertyPath("Value");
binding.Mode = BindingMode.TwoWay;
lblSampleText.SetBinding(TextBlock.FontSize, binding);

  还可通过代码使用BindingOperation类的两个静态方法移除绑定。ClearBinding()方法使用依赖项属性(该属性具有希望删除的绑定)的引用作为参数,而ClearAllBindings()方法为元素删除所有数据绑定;

BindingOperations.ClearAllBindings(lblSampleText);

  ClearBinding()和ClearAllBindings()方法都使用ClearValue()方法,每个元素都从DependencyObject基类继承了ClearValue()方法。ClearValue()方法简单地移除属性的本底值。
  基于标记的绑定比通过代码创建的绑定更常见,因为基于标记的绑定更清晰并且需要完成的工作更少。但在一些特殊情况下,会希望使用代码创建绑定:

  • 创建动态绑定:如果希望根据其他运行时信息修改绑定,或者根据环境常见不同绑定,这是使用代码创建绑定通常更合理
  • 删除绑定:如果希望删除绑定,从而可以通过普通方式设置属性,需要借助CLearBinding()方法或ClearAllBindings()方法。仅为属性应用新值是不够的——如果正在使用双向绑定,设置的值会传播到链接的对象,并且两个属性保持同步。
  • 创建自定义绑定:为让他人能够更容易地修改构建的自定义控件的外观,需要将特定细节从标记移到代码中。

8.1.5 使用代码检索绑定

  可使用代码检索绑定并检查其属性,而不必考虑绑定最初是用代码还是标记创建的。
  可采用两种方式来获取去绑定信息。第一种方式是使用静态方法BindingOperations.GetBinding()来检索响应的Binding对象。这需要提供两个参数 :绑定元素以及具有绑定表达式的属性。

<TextBlock Name="lalSampleText" FontSize="{Binding ElementName=sliderFontSize, Path=Value}"></TextBlock>
Binding binding = BindingOperations.GetBinding(lblSampleText, TextBlock.FontSize);

  一旦拥有绑定对象,就可以检查其属性。日历,绑定元素名Binding.ElementName 提供了绑定表达式的值。Binding.Path提供的PropertyPath对象从绑定对象提取绑定值,Binding.Path.Path获取绑定属性的名称。还有Binding.Mode属性,用于告知绑定何时更新目标元素。
  如果必须在测试时添加诊断代码,绑定对象会有趣一些。但WPF还允许通过调用BindingOperations.GetBindingExpression()方法获得更实用的Binding Expression对象,该方法的参数与GetBinding()方法参数相同:

BindingExpression binding = BindingOperations.GetBindingExpression(lblSampleText, TextBlock.FontSize);

  BindingExpression对象包含一些属性,用于复制Binding对象提供的信息。但迄今为止,最有趣的是ResolveSource属性,该属性允许计算绑定表达式并获取其结果——传递的本地数据。

Slider boundObject = (Slider)expression.ResolvedSource;
string boundData = boundObject.FontSize;

8.1.6 多绑定

<TextBlock Name="LblSampleText">
    <TextBlock.Margin>10</TextBlock.Margin>
    <TextBlock.Text>Simple Text</TextBlock.Text>
    <TextBlock.FontSize>
        <Binding>
            <Binding.ElementName>SliderFontSize</Binding.ElementName>
            <Binding.Path>Value</Binding.Path>
            <Binding.Mode>TwoWay</Binding.Mode>
        </Binding>
    </TextBlock.FontSize>
    <TextBlock.Text>
    	<Binding ElementName="txtContent" Path="Text"></Binding>
    </TextBlock.Text>
    <TextBlock.Foreground>
    	<Binding ElementName="lstColors" Path="SelectedItem.Tag"></Binding>
    </TextBlock.Foreground>
</TextBlock>

  如果希望目标属性受多个源的影响,问题会变得更加有趣——例如,如果希望实用两个相等的合法绑定来设置属性,乍一看,这好像不可能实现。毕竟,当创建绑定时,只能指定一个目标属性。然而,可使用多种方法突破这一限制。
最简单的方式是更改数据绑定的模式 。如前所述,Mode属性允许改变绑定方法,使数值不仅能从源传递到目标,还可以从目标传递到源。可实用项技术创建多个设置同一属性的绑定表达式。其中,最后设置的属性有效。
  为了理解这种方法的工作原理,考虑滑动条示例的一个变体,添加一个能设置精确字体尺寸的文本框。在该例种,可用两种方法设置TextBlock.FontSize属性——通过拖动滑块或在文本框种输入字体尺寸。所有控件都保持同步,因此如果在文本框种输入新的数值,示例文本的字体尺寸就会相应地进行调整,并滑动条上的滑块也会被移动到相应位置。

<StackPanel Margin="5">
    <Slider Name="SliderFontSize">
        <Slider.Margin>3</Slider.Margin>
        <Slider.Minimum>1</Slider.Minimum>
        <Slider.Maximum>40</Slider.Maximum>
        <Slider.TickFrequency>1</Slider.TickFrequency>
        <Slider.TickPlacement>TopLeft</Slider.TickPlacement>
        <Slider.IsSnapToTickEnabled>True</Slider.IsSnapToTickEnabled>
        <Slider.Value>10</Slider.Value>
    </Slider>
    <TextBlock Name="LblSampleText">
        <TextBlock.Margin>10</TextBlock.Margin>
        <TextBlock.Text>Simple Text</TextBlock.Text>
        <TextBlock.FontSize>
            <Binding>
                <Binding.ElementName>SliderFontSize</Binding.ElementName>
                <Binding.Path>Value</Binding.Path>
                <Binding.Mode>TwoWay</Binding.Mode>
            </Binding>
        </TextBlock.FontSize>
    </TextBlock>
    <StackPanel Orientation="Horizontal" Margin="5">
        <TextBlock VerticalAlignment="Center" xml:space="preserve">Exact Size:  </TextBlock>
        <TextBox Name="TxtBound">
            <TextBox.Width>100</TextBox.Width>
            <TextBox.Text>
                <Binding>
                    <Binding.ElementName>LblSampleText</Binding.ElementName>
                    <Binding.Path>FontSize</Binding.Path>
                    <Binding.UpdateSourceTrigger>PropertyChanged</Binding.UpdateSourceTrigger>
                    <Binding.Mode>TwoWay</Binding.Mode>
                    <Binding.Delay>500</Binding.Delay>
                </Binding>
            </TextBox.Text>
        </TextBox>
    </StackPanel>
</StackPanel>

  由于只能为TextBlock.FontSize属性应用数据绑定。合理的做法使保持TextBlock.FontSize属性不变,使其直接绑定到滑动条。尽管不能再为TextBlock.FontSize属性添加另一个绑定,但可将新控件——TextBox控件——绑定到TextBlock.FontSize属性。现在,无论合适TextBlock.FontSize属性发生变化,当前值都会被插入到文本框种。更妙的是,可在文本框种编辑数值以应用特定的尺寸。注意,为了使该例能够工作,TextBox.Text属性必须实用双向绑定,从而使数值能够双向传递。否则,问狂只能显示TextBlock.FontSize属性的值,但不能改变TextBlock.FontSize属性的值。
这个示例存在以下几个问题:

  • 因为Slider.Value属性使双精度类型,所以当拖动滑动条上的滑块时,得到的字体尺寸数值是小数。可通过将TickFrequency属性设置为1(或其他整数间隔),并将IsSnapToTickEnabled属性设置为true,将滑动条的值限制为整数。
  • 在文本框种可输入字符以及其他非数字字符。如果输入了任何内容,文本框的值就不能被解释为数值。因此,数据绑定自动失败,并且字体尺寸会被设置为0。另一个解决方法是处理文本框种的键来主执非法输入,或者实用数据绑定验证。
  • 知道文本框失去焦点,才会应用文本框种的改变。如果这不是所希望的行为,可通过实用Binding对象的UpdateSourceTrigger属性立即进行更新。

8.1.7 绑定更新(目标更新源的方式)

  下表列出的值不影响目标的更新方式,它们仅控制TwoWay或OneWayToSource模式的绑定中源的更新方式。

名称说明
PropertyChanged当目标属性发生变化时立即更新源
LostFocus当目标属性发生变化且失去焦点时更新源
Explicit触发调用BindingExpression.UpdateSource()方法,否则无法更新源
Default根据目标属性的元数据确定更新行为(从技术角度看,时根据FrameworkPropertyMetadata.DefaultUpdateSourceTrigger属性决定更新行为)。大多数属性的默认行为时PropertyChanged,但TextBox.Text属性的默认行为时LostFocus

8.1.8 绑定延迟

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

8.2 绑定到非元素对象

  在数据驱动的应用程序中,更常见的情况是创建从不可见对象中提取数据的绑定表达式。唯一的要求是希望显示的信息必须存储在公有属性中。WPF数据绑定基础结构不能获取私有信息或公有字段。当绑定到非元素对象时,需要放弃Binding.ElementName属性,并实用以下属性中的一个:

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

8.2.1 Source

  最简单的选择是将Source属性指向一些已经准备好了的静态属性。另一种选择是绑定到先前创建好作为资源创建的对象。

<Window.Resources>
	<FontFamily x:Key="CustomFont">Calibri</FontFamily>
</Window.Resources>
<TextBlock Text="Binding Source={x:Static SystemFonts.IconFontFamily}, Path=Source"/>
<TextBlock Text="Binding Source={StaticResource CustomFont}, Path=Source"/>

8.2.2 RelativeSource

  通过RelativeSource属性可根据相对于目标对象的关系指向源对象。例如,可实用RelativeSource属性将元素绑定到自身或其父元素(不知道在元素树种从当前元素到绑定的父元素之间有多少代)。
  为设置Binding.RelativeSource属性,需要使用RelativeSource对象。这会使语法变得更加复杂,因为除了需要创建Binding对象外,还需要在其中创建嵌套的RelativeSource属性。一种选择是使用属性设置语法而不是实用Binding标记扩展。例如,下面的代码为TextBlock.Text属性创建一个Binding对象,这个Binding对象使用查找父窗口并显示窗口标题的RelativeSource对象。

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

8.2.3 DataContext

  在某些情况下,会将大量元素绑定到同一个对象。这时使用FrameworkElement.DataContext属性一次性定义绑定源会更清晰,也更灵活。

<StackPanel DataContext="{x:Static SystemFonts.IconFontFamily}">
	<TextBlock Text="{Binding Path=Source}" />
</StackPanle>

  当在绑定表达式中省略源信息时,WPF会检查元素的DataContext属性。如果属性值为null,WPF会继续向上在元素树种查找第一个不为null的数据上下文。如果找到了一个数据上下文,就为绑定使用找到的数据上下文。如果没有找到,绑定表达式不会为目标属性应用任何值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhy29563

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值