zoukankan      html  css  js  c++  java
  • WPF 绑定及惯用法(二)

    写在前面:这仍然是一些没有经过严格审阅的文字。虽然我的确执行了初稿、复稿以及审阅等一系列用以保证文章质量的方法,但是仍然担心其中是否有错误。希望您能帮助指出,以在下一次我在版本更新时进行修正。所有的错误,包括别字、概念不清(表述错误等)、边缘情况没有覆盖等,您认为有必要提及的各个方面,都可以是我们深入讨论的话题。

      在前面对绑定数据源进行介绍的过程中,本文都是使用Binding类的Source属性指定数据源的。使用该属性访问绑定源具有一些限制:软件开发人员无法引用XAML中定义的元素或依某种规律查找与绑定源相关的元素。因此除了Source属性之外,WPF还提供了ElementName、RelativeSource等方法以辅助完成对绑定源的指定。

      ElementName用来引用(甚至是后向引用)在同一NameScope中显式指定名称的界面组成。绑定将沿其所在的元素沿逻辑树向上查找,直到遇到第一个具有NameScope的元素并尝试在该NameScope中查找该名称。(并不精确,却会是您所接触的大多数情况)

      ElementName属性与Source属性互斥,即在同时设置这两个属性的时候,程序会在运行时抛出异常。在正确引用了所需要指定的界面元素以后,当前绑定的绑定源将会是该界面元素,而具体需要绑定的属性则由Path属性所指定。

      RelativeSource则用来指定与当前界面元素相关的绑定源。使用它进行查找的方式分为几种。查找自身时,使用Self模式:

    1 {Binding RelativeSource={RelativeSource Self}}

      查找祖先的特定类型时,使用FindAncestor模式:

    1 {Binding Path=ActualWidth, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ScrollContentPresenter}}

      绑定应用模板的数据项时,使用TemplatedParent模式:

    1 {Binding RelativeSource={RelativeSource Mode=TemplatedParent}}

      绑定到数据项集合中当前项之前的数据项时,使用PreviousData模式。在网络上使用PreviousData绑定的示例较少,因此在这里,我给出一个较为完整的示例:

     1 <Window 
    2 xmlns:local="clr-namespace:Binding_PreviousData"
    3 x:Name="MainWindow">
    4 <Window.Resources>
    5 <local:HistoryConverter x:Key="historyConverter"/>
    6
    7 <DataTemplate x:Key="historyTemplate">
    8 <TextBlock>
    9 <TextBlock.Text>
    10 <MultiBinding Converter="{StaticResource historyConverter}">
    11 <Binding RelativeSource="{RelativeSource PreviousData}"/>
    12 <Binding/>
    13 </MultiBinding>
    14 </TextBlock.Text>
    15 </TextBlock>
    16 </DataTemplate>
    17 </Window.Resources>
    18 <StackPanel>
    19 <DockPanel>
    20 <Button DockPanel.Dock="Right" Content="Submit" Click="OnButtonClicked"/>
    21 <TextBox x:Name="mInput"/>
    22 </DockPanel>
    23 <ListBox ItemTemplate="{StaticResource historyTemplate}" ItemsSource="{Binding ElementName=MainWindow, Path=History}"/>
    24 </StackPanel>
    25 </Window>
    1 public partial class Window1 : Window
    2 {
    3
    4 public ObservableCollection<string> History…
    5 }

      通过Source、ElementName、RelativeSource等属性提供绑定源的方法彼此拥有一定的互补性。一般说,Source用来引用数据层中的数据,ElementName引用的是UI逻辑树中的组成,而RelativeSource则常常用来从视觉树中查找绑定源,甚至可以穿越当前XAML文件根元素。因此在决定绑定源时,软件开发人员需要根据实际查找方式决定需要使用的方式。

      在绑定中,指定了绑定的源并不足够。请考虑指定绑定源的各种方法:ElementName用来引用逻辑树中所给出的元素;RelativeSource则用来指定与当前元素相关联的其它元素;而Source则较为通用,只是其所接受的各种表达式,如StaticResource,常常用来引用具有特定性质的实例。而绑定所操作的,常常是这些源所具有的各个属性,甚至是子属性。软件开发人员需要一种方法指定参与绑定的绑定源的属性。这也便是绑定提供Path属性及XPath属性的原因。

      一般情况下,绑定的Path所指定的各级属性都需要提供属性更改通知的支持,至少软件开发人员应能保证在需要绑定执行时属性更改通知能及时发出。例如在INotifyPropertyChanged接口的实现中,软件开发人员需要显式地发送PropertyChanged事件。而就DependencyProperty而言,其更改通知的发送则由WPF属性系统辅助解决。另外,绑定的Path属性提供了对结构化数据的支持,如对于Point类型的属性Location,软件开发人员可以设置Path为Location.X。这样做的好处在于,提供结构化数据的支持可以拥有更好的语义特征。另一个优点则在于,更新该结构化数据可以避免其内的各个属性在更新时拥有先后顺序,从而使众多依赖于这些数据的绑定在错误状态中执行绑定逻辑。

      另一个常见的疑惑则是有关绑定的更新:如果在绑定中指明路径为X.Y,并且X发出Y更改的消息,那么绑定是否会执行。答案是会。

      如果绑定的源是XML数据而不是CLR对象,那么软件开发人员需要使用XPath属性指定要使用的绑定源,而不是Path。

      在讲解完绑定的数据源后,请读者来看看绑定的目标。WPF规定绑定的目标属性必须是依赖项属性,而不能是字段等其它组成。您可能心中有疑问:为什么绑定的目标属性必须是依赖项属性?原因很简单:除了对绑定的支持之外,依赖项属性的更改还可能导致布局等功能的变化。如TextBlock的Text属性变化可能会导致TextBlock所需要的空间变大。此时属性更改不仅仅要参与绑定功能的执行,更需要以非常高效的方式参与布局系统以及绘制系统等等各WPF子系统。该高效参与各子系统的方式就是依赖项属性。

      由于一般绑定都使用在XAML中,因此对绑定目标的使用也常常是水到渠成的事情:绑定的目标属性常常是DependencyProperty,而被绑定的属性所处于的实例便自然是DependencyObject。

      如果仅仅提供从源属性到目标属性的联动,那么绑定的作用可能并不那么大。考虑这样一个情况:如果软件开发人员希望将源属性绑定到TextBox的Text属性,那么在用户更改TextBox所显示的文字时,源属性将与目标属性不再匹配。这样的例子有很多,而WPF所提供的解决方案就是绑定的Mode属性。其主要分为四种模式:Twoway、Oneway、OnewayToSource以及OneTime。Twoway模式所提供的功能最为强大。一旦绑定源中参与绑定的属性发生变化,或是绑定的目标属性发生变化,那么绑定都将执行。该模式下的绑定不仅仅会将绑定源属性的变化传递到目标属性,更可以在目标属性发生变化时将变化传递到源属性。这种绑定常常使用在绑定目标属性可以从用户界面更改的情况下,如TextBox的Text属性。另一种模式Oneway可以说是最常用的绑定模式。如果绑定源中参与绑定的属性是一个只读属性,或者绑定的目标属性不会由于其它外界因素所更改,那么Oneway绑定是绑定的最佳选择。与Oneway模式类似的是OnewayToSource模式。该模式可以用来绕过绑定对绑定目标属性的限制:绑定要求其目标属性必须是DependencyProperty,而在某些情况下,软件开发人员需要一个不是DependencyProperty的属性根据一个DependencyProperty属性的变化而变化。最后,正如其名称所表现的一样,OneTime模式仅仅运行一次。

      从性能上来讲,OneWay模式绑定的开销较TwoWay模式的开销小。而OneTime则是较OneWay模式更为轻量级的绑定模式。

      与绑定模式相关的一个知识点则是:在创建一个DependencyProperty作为绑定源时,依赖项属性的元数据中的BindsTwoWayByDefault属性用来控制一个依赖项属性在绑定时是否默认为双向绑定。

      接下来要讲解的则是绑定更新通知。绑定提供了两个附加事件SourceUpdated、TargetUpdated。如果需要激活这两个附加事件,软件开发人员需要将相应的属性NotifyOnSource(Target)Updated设置为True。通过该附加事件,软件开发人员可以将事件处理逻辑与其它界面元素进行互动,从而扩展了绑定执行逻辑的灵活性。另外,一个属性的更新常常与其所处于的更新模式相关。软件开发人员可以通过UpdateSourceTrigger属性确定触发源更新的原因,如TextBox的更新条件。

      除此之外,WPF中的绑定还拥有另外一个模式:异步模式。与该模式相关的属性则为IsAsync。在将该属性的值设置为True的情况下,Binding将会从线程池中取出一个线程处理该绑定,以避免在绑定求值时阻塞UI。在属性的访问符没有返回的时候,绑定会暂时使用FallbackValue作为绑定的值。在没有设置FallbackValue的时候,绑定结果将为绑定目标属性的默认值。该绑定模式在源属性值需要较长时间才能获得的情况下非常有用。

      XmlDataProvider.IsAsynchronous以及ObjectDataProvider.IsAsynchronous属性同样提供了该功能。

      另外一个与绑定相关的重要概念就是主从模式:如果在绑定的实例中将属性Selector.IsSynchronizedWithCurrentItem设置为true,那么当前选定项将与之相关的CollectionView的CurrentItem属性同步。那么其它不接受集合类型数据,只接受单一数据的属性直接绑定到该集合数据项属性的时候实际上是绑定到了该关联CollectionView的CurrentItem属性上。

      另一个问题是:当绑定的目标与绑定源中的属性不一致时该怎么处理?实际上,这就是绑定的转换器所提供的功能。在通过Converter属性标示了绑定所使用的转换器后,绑定的每次运行都会调用该转换器,以将源属性的值转换为所需要的目标属性的值。在使用转换器的时候,软件开发人员还可以通过ConverterParameter属性为转换器标明参数,以允许转换器在转换目标属性的过程中使用该参数。

      实现一个绑定的转换器非常简单:软件开发人员只需要实现IValueConverter或IMultiValueConverter即可:

     1 internal class IntToVisibilityConverter : IValueConverter
    2 {
    3 public Object Convert(object value, Type typeTarget, object param, CultureInfo culture)
    4 {
    5 return (int)value == 0 ? Visibility.Collapsed : Visibility.Visible;
    6 }
    7
    8 public Object ConvertBack(object value, Type typeTarget, object param, CultureInfo culture)
    9 {
    10 throw new NotSupportedException();
    11 }
    12 }

      在实现一个转换器时,软件开发人员最好使用ValueConversion特性修饰此实现,以向开发工具指示转换所涉及的数据类型。

      既然提到了IMultiValueConverter,那么就不得不提到另一种绑定:MultiBinding。该绑定以多个绑定作为子元素,并在每个子绑定的绑定源发生变化后被执行。每个绑定运行的结果都会通过IMultiValueConverter所实现的转化逻辑将源数据转化为目标属性所需要的值。也正是由于这种一个绑定发生变化时MultiBinding就会被执行的特性,软件开发人员需要在使用MultiBinding时不得不考虑一些特殊情况。一个较为严重的情况则是两个相互关联数据所参与的MultiBinding。在其中一个数据发生改变的时候,MultiBinding将被执行以反映数据的更新。但是此时与该数据相关联的其它数据并非处于正确的状态,从而导致错误的结果,更严重地,程序崩溃。其中一个不适合使用MultiBinding的情况就是求子串。如果使用MultiBinding传入需要操作的字符串以及子串的起始位置,那么在任意一个数据源发生改变的时候另一个数据可能是非法的,如在字符串发生变化的时候,子串的起始位置将可能大于字符串的长度。解决该问题的方法则是为这些相关联的信息提供一个结构化的数据,并以其作为绑定的源属性。每次字符串发生变化的时候,软件开发人员创建一个新的该结构化数据,并使其正确记录字符串以及子串的起始位置。在对该结构化数据进行更新时,其内部所记录的数据将是统一的,从而保证绑定的正确执行。

      另一个需要提及的知识点则是子绑定对MultiBinding类属性的继承。MultiBinding类的Mode属性以及UpdateSourceTrigger属性的值将被其各个子绑定继承,除非其中的子绑定重写该属性。

      MultiBinding当前只支持Binding类型的对象,而不支持MultiBinding或PriorityBinding类型的对象。这是因为Converter本身已经支持对这两种对象的模拟。

      除了MultiBinding外,另一个较为特殊的绑定则是PriorityBinding。其同样接受一组Binding作为子元素,并为这些子绑定依次赋予由高到低的优先级。在运行时,PriorityBinding将返回当前成功执行的具有最高优先级的绑定的值。而在所有Binding都没有成功执行或没有返回的情况下,FallbackValue所标示的值将被使用。一般情况下,该绑定用来处理绑定源属性需要较长时间才能成功返回的情况。其与异步模式绑定具有一定的相似性。但不同的是,首先,异步模式绑定所标示的FallbackValue只能在XAML中标明,如果软件开发人员希望FallbackValue会根据数据层状态而改变,那么他需要选择PriorityBinding,并为该PriorityBinding赋予一个具有最低优先级的绑定。同时具有最低优先级的绑定常常是一个同步绑定,以在PriorityBinding中作为FallbackValue使用。其次,异步模式绑定并不支持多个绑定。在需要执行大量耗时操作,却希望给用户一个粗略计算结果的情况下,在PriorityBinding中使用多个Binding并同时执行这两种计算常常是一个较为合适的解决方案。最后,PriorityBinding所提供的异步特性实际上是通过其各个子绑定的异步特性所提供的。

      综上所述,PriorityBinding的这种运行方式决定了各子绑定的运行方式:除了最后一个子绑定外,其它的各个绑定的IsAsync属性需要被设置为True,以令这些耗时功能的执行以异步方式完成,并在完成后对PriorityBinding的运行结果进行适当更新。而对于最后一个子绑定而言,将IsAsync设置为True则不再是一个强制要求。如果IsAsync属性并没有被设置为True,那么最后一个绑定将作为一个更灵活的FallbackValue;如果IsAsync属性并没有被设置为True,那么所有的绑定都将异步执行,绑定所最初显示的则是由PriorityBinding的FallbackValue所指定的数据。

      另外,对PriorityBinding中的各个异步子绑定提供FallbackValue会影响到PriorityBinding的执行。如果您有兴趣,可以自行试验一下。就个人经验而谈,软件开发人员不应设置异步子绑定的FallbackValue。

      现在,您脑中可能有这样一个疑问:看起来,在提供了合适的转换器的情况下,MultiBinding同样可以达成PriorityBinding的效果。对于这个问题,我想我的答案是肯定的。下面就是我为这个问题所提供的简单实现:

     1 <Window 
    2 xmlns:local="clr-namespace:SimulatedPriorityBinding">
    3 <Window.Resources>
    4 <local:PrioritizedConverter x:Key="prioritizedConverter"/>
    5 </Window.Resources>
    6 ……
    7 <MultiBinding Converter="{StaticResource prioritizedConverter}">
    8 <Binding … Path="MoreSlowProperty" IsAsync="True"/>
    9 <Binding … Path="SlowProperty" IsAsync="True"/>
    10 <Binding … Path="QuickProperty"/>
    11 </MultiBinding>
    12 </Window>
     1 public class PrioritizedConverter : IMultiValueConverter
    2 {
    3 public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    4 {
    5 foreach (object value in values)
    6 if (value != DependencyProperty.UnsetValue)
    7 return value;
    8 return DependencyProperty.UnsetValue;
    9 }
    10
    11 public object[] ConvertBack…
    12 }

      其实这种功能的重叠,甚至PriorityBinding只提供MultiBinding所提供功能的子集这一行为并不足以为奇。WPF常常会为一些常用的语法结构提供了简化版本并可能借此提高性能,像TemplateBinding之于使用TemplatedParent模式的Binding。这和C#语言所具有的严格特征略有不同(即C#的前几个版本的设计原则为:一件事情,尽量不提供两种方法去做。举例来说,静态成员函数的调用只能通过类型进行,而C++既可通过类型又可以通过类型实例完成)。

      另一类比较特殊的绑定则是TemplateBinding。其实际上类似于使用了TemplatedParent的Binding,但较Binding所提供的功能更少,也因为更轻量而具有更高的效率。这是因为在TemplateBindingExpression类中,GetValue的实现仅仅是获得特定属性的值,而不是繁琐的计算。由于其固定地绑定到使用该模板的数据实例上,因此该类仅仅提供了Property属性以指定需要绑定到的属性。

      (未完待续)

    源码下载:http://download.csdn.net/detail/silverfox715/3907934

    转载请注明原文地址:http://www.cnblogs.com/loveis715/archive/2011/12/15/2288272.html

    商业转载请事先与我联系:silverfox715@sina.com

  • 相关阅读:
    Kubernetes 再深入一点点
    10分钟完成 mongodb replSet 部署
    网络篇
    p2p 打洞专场(转)
    Dockerfile 编写
    关于github 代码管理,协作开发
    Kubernetes 基于 ubuntu18.04 手工部署 (k8s)
    备忘 ubuntu ip 及 dns 的坑
    各种语言web性能简单对比测试
    vue 按需加载
  • 原文地址:https://www.cnblogs.com/loveis715/p/2288272.html
Copyright © 2011-2022 走看看