Java

WPF 基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)(下)html教程

时间:2013-6-20 23:14:49  作者:WPF之家  来源:http://www.wpf123.com  查看:724  评论:0
内容摘要:十一. 依赖属性元数据  前面我们看到一个依赖属性的注册最全的形式是下面这样子的:public static DependencyProperty Register(string name,                      Type propertyType,     ...

十一. 依赖属性元数据

  前面我们看到一个依赖属性的注册最全的形式是下面这样子的:

public static DependencyProperty Register(string name, 
                     Type propertyType,
                     Type ownerType, 
                     PropertyMetadata typeMetadata,
                     ValidateValueCallback validateValueCallback);

  第一个参数是该依赖属性的名字,第二个参数是依赖属性的类型,第三个参数是该依赖属性的所有者的类型,第五个参数就是一个验证值的回调委托,那么最使我们感兴趣的还是这个可爱的 PropertyMetadata ,也就是我们接下来要讲的元数据。 提到WPF属性元数据,大家可能第一想到的是刚才的PropertyMetadata,那么这个类到底是怎样的呢?我们应该怎样使用它呢?首先我们看它的构造函数(我们选参数最多的来讲):

public PropertyMetadata(object defaultValue,
            PropertyChangedCallback propertyChangedCallback, 
            CoerceValueCallback coerceValueCallback);

  其中的第一个参数是默认值,最后两个分别是PropertyChanged(变化通知)以及Coerce(强制)的两个委托变量,我们在实例化的时候,只需要把这两个委托变量关联到具体的方法上即可。

  事实上,除了PropertyMetadata以外,常见的还有 FrameworkPropertyMetadata,UIPropertyMetadata。他们的继承关系是F->U->P。其中以 FrameworkPropertyMetadata参数最多,亦最为复杂。

  FrameworkPropertyMetadata的构造函数提供了很多重载,我们挑选最为复杂的重载来看它到底有哪些参数以及提供了哪些功能:

public FrameworkPropertyMetadata(object defaultValue,
                 FrameworkPropertyMetadataOptions flags,
                 PropertyChangedCallback propertyChangedCallback, 
                 CoerceValueCallback coerceValueCallback,
                 bool isAnimationProhibited,
                 UpdateSourceTrigger defaultUpdateSourceTrigger);

  其中第一个参数是默认值,最后两个参数分别是是否允许动画,以及绑定时更新的策略(在Binding当中相信大家并不陌生),这个不详细解释了。重点看一下里第三、四两个参数,两个 CallBack的委托。结合前面Register的时候提到的ValidateValueCallback共组成三大”金刚“,这三个Callback 分别代表Validate(验证),PropertyChanged(变化通知)以及Coerce(强制)。当然,作为 Metadata,FrameworkPropertyMetadata只是储存了该依赖属性的策略信息,WPF属性系统会根据这些信息来提供功能并在适当的时机回调传入的delegate,所以最重要的还是我们定义的这些方法,通过他们传入委托才能起到真正的作用。

  上面讲了元数据暴露给我们的构造函数,其实在其内部还提供了两个方法,这个在做自定义控件的时候,也很值得注意:

protected virtual void Merge(PropertyMetadata baseMetadata, DependencyProperty dp)
{
  // 实现元数据继承之间的合并
}
protected virtual void OnApply(DependencyProperty dependencyProperty, Type targetType)
{
  // 当元数据被这个属性应用,OnApply就会被触发,在此时元数据也将被密封起来。
}

  前面讲了这么多,那么我们现在就来看看依赖属性回调、验证及强制值到底是怎么使用的呢?大家千万要坚持住,后面内容更加精彩!

  十二. 依赖属性回调、验证及强制值

  我们通过下面的这幅图,简单介绍一下WPF属性系统对依赖属性操作的基本步骤:

WPF 基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)(下)html教程

  wpf123.com" style="text-decoration: underline; color: rgb(62, 98, 166); " target="_blank">  查看原图(大图)

  第一步,基础值就是上面“六.依赖属性的优先级”提供的那些显示设置,所以它的优先级比较好确定,但有些不会按常规出牌,所以也需要注意总结。

  第二步,如果依赖属性值是计算表达式 (如前面示例中的绑定等语法特性),这个时候就会计算表达式的结果作为第二步的值。

  第三步,动画是一种优先级很高的特殊行为,很多时候,我们都会听到动画优先的声音,所以它的优先级高于其他基础设置;

  第四步,强制则是注册时提供的 CoerceValueCallback 委托,它负责验证属性值是否在允许的限制范围之内,和我们对属性的验证一样,比如强制设置该值必须大于于0小于10等等;

  第五步,验证是指我们注册依赖属性所提供的 ValidateValueCallback 委托方法,它最终决定了属性值设置是否有效,当数据无效时会抛出异常来通知。

  前面我们讲了基本的流程,下面我们就用一个小的例子来进行说明:

namespace SampleProcess_DPs
{
  class Program
  {
    static void Main(string[] args)
    {
      SimpleDPClass sDPClass = new SimpleDPClass();
      sDPClass.SimpleDP = 8;
      Console.ReadLine();
    }
  }
  public class SimpleDPClass : DependencyObject
  {
    public static readonly DependencyProperty SimpleDPProperty =
      DependencyProperty.Register("SimpleDP", typeof(double), typeof(SimpleDPClass),
        new FrameworkPropertyMetadata((double)0.0,
          FrameworkPropertyMetadataOptions.None,
          new PropertyChangedCallback(OnValueChanged),
          new CoerceValueCallback(CoerceValue)),
          new ValidateValueCallback(IsValidValue));
    public double SimpleDP
    {
      get { return (double)GetValue(SimpleDPProperty); }
      set { SetValue(SimpleDPProperty, value); }
    }
    private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      Console.WriteLine("当值改变时,我们可以做的一些操作,具体可以在这里定义: {0}", e.NewValue);
    }
    private static object CoerceValue(DependencyObject d, object value)
    {
      Console.WriteLine("对值进行限定,强制值: {0}", value);
      return value;
    }
    private static bool IsValidValue(object value)
    {
      Console.WriteLine("验证值是否通过,返回bool值,如果返回True表示严重通过,否则会以异常的形式暴露: {0}", value);
      return true;
    }
  }
}

  结果如下:

wpf123.com">WPF 基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)(下)html教程

    查看原图(大图)

  当SimpleDP属性变化之后,PropertyChangeCallback就会被调用。可以看到结果并没有完全按照我们先前的流程先 Coerce后Validate的顺序执行,有可能是WPF内部做了什么特殊处理,当属性被修改时,首先会调用Validate来判断传入的value是否有效,如果无效就不继续后续的操作,这样可以更好的优化性能。从上面的结果上看出,CoerceValue后面并没有立即ValidateValue,而是直接调用了PropertyChanged。这是因为前面已经验证过了value,如果在Coerce中没有改变value,那么就不用再验证了。如果在 Coerce中改变了value,那么这里还会再次调用ValidateValue操作,和前面的流程图执行的顺序一样,在最后我们会调用 ValidateValue来进行最后的验证,这就保证最后的结果是我们希望的那样了(正如打游戏一样,打了小怪,在最后过总关的时候还是需要打大怪才能闯关的)。

  上面简单介绍了处理流程,下面我们就以一个案例来具体看一看上面的流程到底有没有出入,这个例子改编于Sacha Barber 的Dependency Properties代码示例,我相信通过这段代码你会对这个上面讲的概念有更清晰地认识。

  UI很简单,黄色部分显示当前值,我们在初始化的时候把它设置为100,然后它的最小值和最大值分别设置为0和500,按钮”设置为-100 “企图把当前值设为-100,按钮”设置为1000“试图把当前值设为1000。具体大家看代码(我都写了注释,很容易理解的).

WPF 基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)(下)html教程

wpf123.com">    查看原图(大图)

  依赖属性代码文件如下:

namespace Callback_Validation_DPs
{
  public class Gauge : Control
  {
    public Gauge() : base() { }
    //注册CurrentReading依赖属性,并添加PropertyChanged、CoerceValue、ValidateValue的回调委托
    public static readonly DependencyProperty CurrentReadingProperty = DependencyProperty.Register(
      "CurrentReading",
      typeof(double),
      typeof(Gauge),
      new FrameworkPropertyMetadata(
        Double.NaN,
        FrameworkPropertyMetadataOptions.None,
        new PropertyChangedCallback(OnCurrentReadingChanged),
        new CoerceValueCallback(CoerceCurrentReading)
      ),
      new ValidateValueCallback(IsValidReading)
    );
    //属性包装器,通过它来暴露CurrentReading的值
    public double CurrentReading
    {
      get { return (double)GetValue(CurrentReadingProperty); }
      set { SetValue(CurrentReadingProperty, value); }
    }
    //注册MinReading依赖属性,并添加PropertyChanged、CoerceValue、ValidateValue的回调委托
    public static readonly DependencyProperty MinReadingProperty = DependencyProperty.Register(
    "MinReading",
    typeof(double),
    typeof(Gauge),
    new FrameworkPropertyMetadata(
      double.NaN,
      FrameworkPropertyMetadataOptions.None,
      new PropertyChangedCallback(OnMinReadingChanged),
      new CoerceValueCallback(CoerceMinReading)
    ),
    new ValidateValueCallback(IsValidReading));
    //属性包装器,通过它来暴露MinReading的值
    public double MinReading
    {
      get { return (double)GetValue(MinReadingProperty); }
      set { SetValue(MinReadingProperty, value); }
    }
    //注册MaxReading依赖属性,并添加PropertyChanged、CoerceValue、ValidateValue的回调委托
    public static readonly DependencyProperty MaxReadingProperty = DependencyProperty.Register(
      "MaxReading",
      typeof(double),
      typeof(Gauge),
      new FrameworkPropertyMetadata(
        double.NaN,
        FrameworkPropertyMetadataOptions.None,
        new PropertyChangedCallback(OnMaxReadingChanged),
        new CoerceValueCallback(CoerceMaxReading)
      ),
      new ValidateValueCallback(IsValidReading)
    );
    //属性包装器,通过它来暴露MaxReading的值
    public double MaxReading
    {
      get { return (double)GetValue(MaxReadingProperty); }
      set { SetValue(MaxReadingProperty, value); }
    }
    //在CoerceCurrentReading加入强制判断赋值
    private static object CoerceCurrentReading(DependencyObject d, object value)
    {
      Gauge g = (Gauge)d;
      double current = (double)value;
      if (current < g.MinReading) current = g.MinReading;
      if (current > g.MaxReading) current = g.MaxReading;
      return current;
    }

    //当CurrentReading值改变的时候,调用MinReading和MaxReading的CoerceValue回调委托
    private static void OnCurrentReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      d.CoerceValue(MinReadingProperty); 
      d.CoerceValue(MaxReadingProperty); 
    }
    //当OnMinReading值改变的时候,调用CurrentReading和MaxReading的CoerceValue回调委托
    private static void OnMinReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      d.CoerceValue(MaxReadingProperty);
      d.CoerceValue(CurrentReadingProperty);
    }
    //在CoerceMinReading加入强制判断赋值
    private static object CoerceMinReading(DependencyObject d, object value)
    {
      Gauge g = (Gauge)d;
      double min = (double)value;
      if (min > g.MaxReading) min = g.MaxReading;
      return min;
    }
    //在CoerceMaxReading加入强制判断赋值
    private static object CoerceMaxReading(DependencyObject d, object value)
    {
      Gauge g = (Gauge)d;
      double max = (double)value;
      if (max < g.MinReading) max = g.MinReading;
      return max;
    }
    //当MaxReading值改变的时候,调用MinReading和CurrentReading的CoerceValue回调委托
    private static void OnMaxReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      d.CoerceValue(MinReadingProperty); 
      d.CoerceValue(CurrentReadingProperty); 
    }
    //验证value是否有效,如果返回True表示验证通过,否则会提示异常
    public static bool IsValidReading(object value)
    {
      Double v = (Double)value;
      return (!v.Equals(Double.NegativeInfinity) && !v.Equals(Double.PositiveInfinity));
    }
  }
}

  XAML代码如下:

<Window x:Class="Callback_Validation_DPs.Window1"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="clr-namespace:Callback_Validation_DPs" 
  WindowStartupLocation="CenterScreen" 
  Title="Callback_Validation_DPs" Height="400" Width="400">
  <StackPanel Orientation="Vertical">
    <local:Gauge x:Name="gauge1" MaxReading="100" MinReading="0" />
    <Label Content="可以设置最小值为0和最小大值为500" Height="30"/>
    <StackPanel Orientation="Horizontal" Height="60">
      <Label Content="当前值为 : "/>
      <Label Background="Yellow" BorderBrush="Black" BorderThickness="1" 
          IsEnabled="False" Content="{Binding ElementName=gauge1, Path=CurrentReading}" Height="25" VerticalAlignment="Top" />
    </StackPanel>
    <Button x:Name="btnSetBelowMin" Content="设置为 -100" 
        Click="btnSetBelowMin_Click"/>
    <Button x:Name="btnSetAboveMax" Content="设置为 1000" 
        Click="btnSetAboveMax_Click"/>
  </StackPanel>
</Window>

  XAML的后台代码如下:

public partial class Window1 : Window
{
  public Window1()
  {
    InitializeComponent();
    //设置CurrentReading的值,这个时候会触发哪些变化?调试代码吧!
    gauge1.CurrentReading = 100;
  }
  private void btnSetBelowMin_Click(object sender, RoutedEventArgs e)
  {
    //设置CurrentReading的值,这个时候会触发哪些变化?调试代码吧!
    gauge1.CurrentReading = -100;
  }
  private void btnSetAboveMax_Click(object sender, RoutedEventArgs e)
  {
    //设置CurrentReading的值,这个时候会触发哪些变化?调试代码吧!
    gauge1.CurrentReading = 10000;
  }
}

  在上面的例子中,一共有三个依赖属性相互作用——CurrentReading、MinReading和MaxReading,这些属性相互作用,但它们的规则是MinReading≤CurrentReading≤MaxReading。根据这个规则,当其中一个依赖属性变化时,另外两个依赖属性必须进行适当的调整,这里我们要用到的就是CoerceValue这个回调委托,那么实现起来也非常的简单,注册MaxReading的时候加入 CoerceValueCallback,在CoerceMaxReading函数中做处理:如果Maximum的值小于MinReading,则使 MaxReading值等于MinReading;同理在CurrentReading中也加入了CoerceValueCallback进行相应的强制处理。然后在MinReading的ChangedValueCallback被调用的时候,调用CurrentReading和MaxReading的 CoerceValue回调委托,这样就可以达到相互作用的依赖属性一变应万变的”千机变“。

  换句话说,当相互作用的几个依赖属性其中一个发生变化时,在它的PropertyChangeCallback中调用受它影响的依赖属性的CoerceValue,这样才能保证相互作用关系的正确性。 前面也提高ValidateValue主要是验证该数据的有效性,最设置了值以后都会调用它来进行验证,如果验证不成功,则抛出异常。

  十三. 依赖属性监听

  如果想监听依赖属性的改变,可以用两种方法实现,在很多时候,我们两种方法都会用到:

  用DependencyPropertyDescriptor 比较简便,在代码里面写起来也比较便捷;

  用OverrideMetadata的方式主要在自定义控件以及处理一些类间关系的时候;

  第一种方法:派生自这个类,然后定义它的属性,重写属性的原数据并传递一个PropertyChangedCallBack参数即可,如下代码:

public class MyTextBox : TextBox
{
  public MyTextBox(): base()
  {
  }
  static MyTextBox()
  {
    //第一种方法,通过OverrideMetadata
    FlowDirectionProperty.OverrideMetadata(typeof(MyTextBox), new FrameworkPropertyMetadata(new PropertyChangedCallback(FlowDirectionPropertyChanged)));
  }
  private static void FlowDirectionPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
  {
    ((MyTextBox)sender).FontWeight = (((MyTextBox)sender).FlowDirection == FlowDirection.LeftToRight) ? FontWeights.Bold : FontWeights.Normal;
  }
}

  第二种方法:这个方法更加简单,获取DependencyPropertyDescriptor并调用AddValueChange()为其挂接一个回调函数,如下代码:

private void Window1_Loaded(object sender, RoutedEventArgs e)
 {
   //第二种方法,通过OverrideMetadata
   DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
   descriptor.AddValueChanged(tbxEditMe, tbxEditMe_TextChanged);
 }
 private void tbxEditMe_TextChanged(object sender, EventArgs e)
 {
   MessageBox.Show("", "Changed");
 }

  十四. 代码段(自动生成)

  代码段可以说是一个非常普遍且实用的功能,我们可以利用它来简化和规范我们的代码。在项目当中我们通常会定义大量的代码段,如怎样写一个类、怎样定义一个方法、公用代码库等等都可以定义成代码段,今天不着重讲这一块,下面就来看看在默认的VS中有哪些代码段

WPF 基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)(下)html教程

  wpf123.com" style="text-decoration: underline; color: rgb(62, 98, 166); " target="_blank">  查看原图(大图)

  上面看到的是visual basic的代码段(一不小心截图截错了,呵呵),但不幸的是针对C#的代码段却很少。不过没关系,既然默认没有提供那么多代码段,我们可以自己动手嘛,正所谓自己动手丰衣足食嘛!相信大家都有自定义代码段的经历,同时在网上也有很多好的代码段下载,我用得最多的是DrWPFSnippets,由于接触 WPF和Silverlight是在07年,所以当时自己也定义过一些代码段,由于自己主要精力还是在技术架构、ASP.NET、WCF、OO等方面,所以在08年以后就开始使用网上的代码段资源了,当然由于之前项目也自己写了一些代码段,所以很多时候都是混合起来使用,大家可以到http://drwpf.com/blog/2010/04/30/updated-code-snippets/去下载,这个代码段包最早从2007年11月就提供下载了,在今年四月份进行了升级,同时支持VS2005/VS2008/VS2010,所以大家可以下载下来体验一下,很不错的哦!下载以后点击DrWPFSnippets.vsi就会自动安装,安装完成以后,你会看到如下界面,图中的Shortcut就是你要按的快捷键,不过生成的代码会出现有些帮助类找不到的情况,如RoutedEvent会生成一个RoutedEventHelper的类,这个是没有关系的,你到网上一搜就可以把这个类加入到你的代码当中。那么运行就十分正常了。在安装的时候提醒一下,最好一次安装成功,否则你会为众多的弹窗口感到十分厌恶,呵呵!

WPF 基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)(下)html教程

wpf123.com">    查看原图(大图)

  那么现在你就可以在项目当中使用了,如按下re+TAB键两次,你就会看到如下界面,然后选择你的选项即可生成需要的代码(这里re就是Routed event的简写)。

WPF 基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)(下)html教程

  wpf123.com" style="text-decoration: underline; color: rgb(62, 98, 166); " target="_blank">  查看原图(大图)

  如下是生成的代码,你可以直接使用或者经过适当修改使用。

  #region Swindle
/// <summary>
/// Swindle Routed Event
/// </summary>
public static readonly RoutedEvent SwindleEvent = EventManager.RegisterRoutedEvent("Swindle", 
  RoutingStrategy.Bubble, typeof(TrioEventHandler), typeof(Window1));
/// <summary>
/// Occurs when ...
/// </summary>
public event TrioEventHandler Swindle
{
  add { AddHandler(SwindleEvent, value); }
  remove { RemoveHandler(SwindleEvent, value); }
}
/// <summary>
/// A helper method to raise the Swindle event.
/// </summary>
/// <param name="arg"> </param>
/// <param name="arg2"> </param>
/// <param name="arg3"> </param>
protected TrioEventArgs RaiseSwindleEvent(bool arg, bool arg2, bool arg3)
{
  return RaiseSwindleEvent(this, arg, arg2, arg3);
}
/// <summary>
/// A static helper method to raise the Swindle event on a target element.
/// </summary>
/// <param name="target">UIElement or ContentElement on which to raise the event</param>
/// <param name="arg"> </param>
/// <param name="arg2"> </param>
/// <param name="arg3"> </param>
internal static TrioEventArgs RaiseSwindleEvent(DependencyObject target, bool arg, bool arg2, bool arg3)
{
  if (target == null) return null;
  TrioEventArgs args = new TrioEventArgs(arg, arg2, arg3);
  args.RoutedEvent = SwindleEvent;
  RoutedEventHelper.RaiseEvent(target, args);
  return args;
}
#endregion

  十五. 模拟依赖属性实现

  古人有”不入虎穴焉得虎子“的名句,我们今天也试着入一入虎穴,探探依赖属性里面到底藏着什么不可告人的秘密,在往下讲之前,我们先来看一下DependencyObject 、DependencyProperty 以及PropertyMetadata到底包含哪些功能,如下面三幅图

WPF 基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)(下)html教程

  wpf123.com" style="text-decoration: underline; color: rgb(62, 98, 166); " target="_blank">  查看原图(大图)

WPF 基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)(下)html教程

wpf123.com">    查看原图(大图)

WPF 基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)(下)html教程

 

  通过前面三幅图,我们就可以了解WPF依赖属性系统的大体结构以及主要功能,再者通过前面我们对它的使用,对它的内部实现也有一个相对比较清晰的认识,那么接下来要做的就是:借助Reflector+VS调试内部代码功能一起来研究其内部的实现原理。 本来想详细写清楚开发的过程,但是有点多,所以我打算直接讲这几个类。大家也可以通过这个思路来试一试,同时还可以参考Mono的源码、WF的依赖属性源码等。这里要推荐的是周永恒的博客,此人对技术的理解很是透彻,博文虽少,但每篇都堪称经典,所以他的文章,我都通读三遍。虽然大多概念都懂,并且读到深处也能产生共鸣,其最主要目的还是学习他这种”阐述问题的思路“,后来也和此人MSN聊过几次。所以这个依赖属性的框架在某些程度上也借鉴了他的一些写法。


标签:WPF 依赖属性  

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。本文出自“wpf之家”,请务必保留此出处:http://www.wpf123.com

相关评论
Copyright © 2009-2014 WPF之家(http://www.wpf123.com/) All rights reserved
 Powered by WPF之家
鄂ICP备13006396号