WPF 绑定及惯用法(三)

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

三.其它问题

  在绑定中,软件开发人员可以通过Source、ElementName以及RelativeSource等属性标明绑定源。实际上,对这些属性的使用实际上都是完成了对DataContext属性所记录的默认绑定源的重写。DataContext属性用来记录用户界面元素在参与数据绑定时所使用的绑定源。在为一个元素设置了DataContext属性之后,其各个子元素将继承该DataContext值,除非该子元素显式地为DataContext属性赋予了新值。在需要绑定到DataContext上的时候,软件开发人员可以在XAML中直接使用Binding而不再显式地对绑定源进行设置。

  而在XAML中,软件开发人员常常可以通过绑定为DataContext赋值。为了能让该DataContext被更多元素使用,对DataContext的赋值常发生在较高的层次上,如XAML的根元素。

  但是在通过绑定设置了DataContext的情况下,软件开发人员需要注意在何时DataContext才能作为有效值。这是因为绑定的运行并不是在根元素的构造函数调用完成后即已经执行完毕。因此在根元素的InitializeComponent()函数调用完毕以后,DataContext可能并不可用。

  在知道了DataContext的含义之后,我们就可以开始讲解如何调试绑定了。很多人会在编写绑定时不知如何对其进行调试。实际上,绑定的错误常常是绑定源在XAML中标示错误。而就这方面而言,绑定的调试十分简单,只需要您使用一些小技巧。

  在进入该议题之前,本文先来强调一下绑定成功执行的条件:

  1. 指定正确的绑定源。
  2. 转换器能正确地执行源属性的转化。
  3. 经过转换器转换的数值能被赋予目标属性。

  首先,软件开发人员需要在绑定中添加一个转换器。该转换器不做任何事情,只需要它返回符合绑定目标的特定值,如绑定目标为int类型时,它就返回1。该转换器允许软件开发人员在绑定运行过程中设置断点,查看绑定所传入的绑定源。

  无论是使用Source、RelativeSource、ElementName,对这些属性进行指定的XAML语法都较为简单。因此软件开发人员可以通过转换器逐渐调整该绑定源,直至其正确为止。在正确地标示了绑定源之后,软件开发人员可以逐渐修改绑定的Path属性,直到转换器的输入为预定的源属性为止。

  在成功地标示了绑定源之后,软件开发人员需要添加代表实际转换逻辑的转换器。在该转换器中,软件开发人员需要保证所有的返回值都是目标属性所具有的类型。

  另外一种方法则是使用类型System.Diagnostics.PresentationTraceSources类所提供的TraceLevel附加属性。在绑定中标明了该附加属性之后,该绑定在解析和运行时所产生的众多信息都会显示在屏幕上。

  如果在转换器运行过程中发生了异常情况,软件开发人员可以通过Binding.DoNothing以及DependencyProperty.UnsetValue控制绑定的运行。如果绑定的源属性或者转换器返回了Binding.DoNothing,那么绑定引擎将不会执行任何后续操作,如指示绑定引擎不要将值传输到绑定目标、不要移动到PriorityBinding中的下一个Binding,或者不要使用FallBackValue或默认值。即类似取消本次绑定计算的功能。与之对应的是DependencyProperty.UnsetValue。如果绑定执行的三个步骤:绑定源路径解析成功、值转换器能够转换结果并且结果对绑定目标有效都成功执行,那么绑定成功执行。任何一步所返回的DependencyProperty.UnsetValue都表示绑定运行发生了异常且绑定将返回FallbackValue所记录的值。如果没有设置FallbackValue,那么将会使用目标属性的默认值。

  另一个棘手的问题则是绑定的失效。有时候,您会发现在程序开始时还能正常运行的绑定失效了。就个人经验而言,绑定的失效主要分为两种情况:对于One-way绑定而言,如果软件开发人员绕过绑定直接更改了目标属性,那么绑定将会失效。而对于Two-way绑定而言,如果软件开发人员没有通过绑定直接更改了目标属性,而目标属性对源属性的更新由于抛出异常等原因失败,那么绑定也将失效。

  最后一个与DataContext相关的知识点则是DataTemplate对DataContext的更改。DataTemplate会将DataContext更改为使用DataTemplate的数据项。

  接下来要讨论的则是BindingExpression。也许您会奇怪为什么会在提供了Binding类的情况下再提供BindingExpression类。这两个类型之间的不同在于:BindingExpression类用来表示绑定功能的实例,而Binding类则用来记录绑定功能的公有信息。也就是说,每个绑定实例都对应着同一个BindingExpression类,以保持绑定源与绑定目标之间的连接,记录着绑定的运行状态,如Status、HasError以及ValidationError属性。而相同的绑定则共享着同一个Binding类实例。

  和绑定拥有不同的形式一样,BindingExpression类也有相似的组织形式,如PriorityBindingExpression以及MultiBindingExpression。PriorityBindingExpression类的成员属性ActiveBindingExpression可以用来获取当前活动的BindingExpression对象。TemplateBinding同样拥有一个对应的TemplateBindingExpression。

四.常用方法

  本节中,本文将讲解一些有关绑定的常用方法。

  首先要讲解的则是延迟绑定。产生该解决方案的原因是为了提高程序的启动性能。请想象下面一种情况:在一个程序的XAML中声明的绑定会在程序启动时加载,并请求绑定源属性的值。对该源属性值的求解将会导致其它功能被加载。试想一下,如果Ribbon所罗列的所有功能都会在程序启动时被加载,那么程序的启动性能将变得非常差。

  这也就是延迟绑定所需要解决的问题。只有在程序界面变为可见时,绑定才会被添加到界面元素中并对其进行求解。

  您可能第一反应是创建一个自定义绑定以解决该问题。的确,BindingBase类提供了虚函数CreateBindingExpressionOverride()以供自定义绑定实现者提供自定义功能。但是本文不采用该方法,其原因有二:该函数所提供的灵活性较差;该函数具有较强的语义特征,并不适用于延迟绑定的实现。

  因此,使LazyBinding派生自MarkupExtension并重写它的ProvideValue()函数可能是一个更好的选择。下面就是实现LazyBinding的代码:

 1 [MarkupExtensionReturnType(typeof(object))]
2 public class LazyBindingExtension : MarkupExtension
3 {
4 public LazyBindingExtension()
5 { }
6
7 public LazyBindingExtension(string path)
8 {
9 Path = new PropertyPath(path);
10 }
11
12 public override object ProvideValue(IServiceProvider serviceProvider)
13 {
14 IProvideValueTarget service = serviceProvider.GetService
15 (typeof(IProvideValueTarget)) as IProvideValueTarget;
16 if (service == null)
17 return null;
18
19 mTarget = service.TargetObject as FrameworkElement;
20 mProperty = service.TargetProperty as DependencyProperty;
21 if (mTarget != null && mProperty != null)
22 {
23 mTarget.IsVisibleChanged += OnIsVisibleChanged;
24 return null;
25 }
26 else
27 {
28 Binding binding = CreateBinding();
29 return binding.ProvideValue(serviceProvider);
30 }
31 }
32
33 private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
34 {
35 Binding binding = CreateBinding();
36 BindingOperations.SetBinding(mTarget, mProperty, binding);
37 }
38
39 private Binding CreateBinding()
40 {
41 Binding binding = new Binding(Path.Path);
42 if (Source != null)
43 binding.Source = Source;
44 if (RelativeSource != null)
45 binding.RelativeSource = RelativeSource;
46 if (ElementName != null)
47 binding.ElementName = ElementName;
48 binding.Converter = Converter;
49 binding.ConverterParameter = ConverterParameter;
50 return binding;
51 }
52
53 #region Fields
54 private FrameworkElement mTarget = null;
55 private DependencyProperty mProperty = null;
56 #endregion
57
58 #region Properties
59 public object Source…
60 public RelativeSource RelativeSource…
61 public string ElementName…
62 public PropertyPath Path…
63 public IValueConverter Converter…
64 public object ConverterParameter…
65 #endregion
66 }

  在这里,本文仅仅探测IsVisibileChanged事件,以在UI元素显示时动态添加绑定。在该类的真正实现中,以何种方式完成延迟功能则是您需要根据需求决定。

  在XAML中,软件开发人员可以像普通绑定一样使用它。但需要注意的一个问题就是MarkupExtension的嵌套使用。如果您按照下面的方法使用LazyBinding:

1 <TextBlock Text="{local:LazyBinding ElementName=mMainWindow, Path=Source, Converter={StaticResource testConverter}}"/>

  那么编译器会在编译时报错。从网络上的讨论来看,这是一个Bug,但是无论在VS2008还是VS2010中,其都没有得到修正。如果我是错误的,请通知我。

  作为一个变通的方法,我们可以在程序中通过XML元素的方法完成对LazyBinding的使用:

1 <TextBlock>
2 <TextBlock.Text>
3 <local:LazyBinding ElementName="mMainWindow" Path="Source" Converter="{StaticResource testConverter}"/>
4 </TextBlock.Text>
5 </TextBlock>

  在一个大型程序中,软件开发人员可能需要为绑定编写众多的Converter,而这些Converter中可能存在着部分重复的执行逻辑。为了能够重用这些执行逻辑,软件开发人员可以通过一个Converter将多个子Converter组合起来:

1 <local:CompositeConverter x:Key="compositeConverter">
2 <local:StringToBooleanConverter/>
3 <BooleanToVisibilityConverter/>
4 </local:CompositeConverter>

  CompositeConverter的实现如下:

 1 [ContentProperty("Converters")]
2 public class CompositeConverter : IValueConverter
3 {
4 public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
5 {
6 foreach (IValueConverter converter in Converters)
7 {
8 value = converter.Convert(value, targetType, parameter, culture);
9 if (value == DependencyProperty.UnsetValue
10 || value == Binding.DoNothing)
11 break;
12 }
13 return value;
14 }
15
16 public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
17 {
18 for (int index = Converters.Count - 1; index >= 0; index--)
19 {
20 value = Converters[index].ConvertBack(value, targetType,
21 parameter, culture);
22 if (value == DependencyProperty.UnsetValue
23 || value == Binding.DoNothing)
24 break;
25 }
26 return value;
27 }
28
29 public List<IValueConverter> Converters
30 {
31 get { return mConverters; }
32 }
33
34 private List<IValueConverter> mConverters = new List<IValueConverter>();
35 }

  接下来要解决的问题则是使用转换器时的繁琐:在需要使用一个转换器的时候,软件开发人员常常需要在资源中声明转换器实例,并在需要使用该转换器的时候通过StaticResource标记扩展等方法引用它。如果该转换器在不同的文件中使用,那么软件开发人员需要再次在资源中声明转换器,或在公共资源文件中添加该转换器(但注意,公共资源文件将不会被编译为baml,其加载速度较baml慢)。

  相反地,如果能在程序中通过一个标记扩展就能完成对特定转换器的创建及引用,那么软件开发人员所需要做的事情就非常简单了。而下面就是针对该方法给出的解决方案:

 1 public class ConverterFactoryExtension : MarkupExtension
2 {
3 public override object ProvideValue(IServiceProvider serviceProvider)
4 {
5 Assembly assembly = Assembly.GetExecutingAssembly();
6 Type type = assembly.GetType(ConverterName);
7 if (type != null)
8 {
9 ConstructorInfo defCons = GetDefaultConstructor(type);
10 if (defCons != null)
11 return defCons.Invoke(new object[] {});
12 }
13 return null;
14 }
15
16 private ConstructorInfo GetDefaultConstructor(Type type)
17 {
18 ConstructorInfo[] infos = type.GetConstructors();
19 foreach (ConstructorInfo info in infos)
20 {
21 ParameterInfo[] paramInfos = info.GetParameters();
22 if (paramInfos.Length == 0)
23 return info;
24 }
25 return null;
26 }
27
28 public string ConverterName…
29 }

  在XAML使用该标记扩展的方法为:

1 <TextBlock Text="{Binding … Converter={local:ConverterFactory ConverterName=Converter_Factory.TestConverter}}"/>

  其实,这也是针对上面所提到的不能使用内嵌标记扩展的解决方案。软件开发人员可以使用这种方法在自定义绑定中提供对转换器的支持。当然,该解决方案还有很多情况需要考虑:没有提供对在其它程序集中定义的转换器类型的支持等等。
  (完)

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

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

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

原文地址:https://www.cnblogs.com/loveis715/p/2289641.html