六、依赖属性回调、验证及强制值

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

  借用一个常见的图例,介绍一下WPF属性系统对依赖属性操作的基本步骤:

  • 第一步,确定Base Value,对同一个属性的赋值可能发生在很多地方。比如控件的背景(Background),可能在Style或者控件的构造函数中都对它进行了赋值,这个Base Value就要确定这些值中优先级最高的值,把它作为Base Value。
  • 第二步,估值。如果依赖属性值是计算表达式(Expression),比如说一个绑定,WPF属性系统就会计算表达式,把结果转化成一个实际值。
  • 第三步,动画。动画是一种优先级很高的特殊行为。如果当前属性正在作动画,那么因动画而产生的值会优于前面获得的值,这个也就是WPF中常说的动画优先。
  • 第四步,强制。如果我们在FrameworkPropertyMetadata中传入了 CoerceValueCallback委托,WPF属性系统会回调我们传入的的delagate,进行属性值的验证,验证属性值是否在我们允许的范围之内。例如强制设置该值必须大于于0小于10等等。在属性赋值过程中,Coerce拥有 最高的优先级,这个优先级要大于动画的优先级别。
  • 第五步,验证。验证是指我们注册依赖属性如果提供了ValidateValueCallback委托,那么最后WPF会调用我们传入的delegate,来验证数据的有效性。当数据无效时会抛出异常来通知。

  那么应该如何使用这些功能呢?

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

XAML的代码如下:

<Window x:Class="WpfApp1.WindowValid"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title=" WindowValid " Height="300" Width="400">

    <Grid>

        <StackPanel>     

            <Button Name="btnDPTest" Click="btnDPTest_Click" >属性值执行顺序测试</Button>
</StackPanel> </Grid> </Window>

C#的代码如下:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading;

using System.Threading.Tasks;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Shapes;

using System.Windows.Threading;

using WpfApp1.Models;

namespace WpfApp1

{

    /// <summary>

    /// WindowThd.xaml 的交互逻辑

    /// </summary>

    public partial class WindowValid: Window

    {

        public WindowValid ()

        {

            InitializeComponent();

    }
private void btnDPTest_Click(object sender, RoutedEventArgs e)
{ SimpleDP test = new SimpleDP(); test.ValidDP = ; } } } using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; namespace WpfApp1.Models { public class SimpleDP : DependencyObject { public static readonly DependencyProperty ValidDPProperty = DependencyProperty.Register("ValidDP", typeof(int), typeof(SimpleDP), new FrameworkPropertyMetadata(, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnValueChanged), new CoerceValueCallback(CoerceValue)), new ValidateValueCallback(IsValidValue)); public int ValidDP { get { return (int)GetValue(ValidDPProperty); } set { SetValue(ValidDPProperty, value); } } private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Console.WriteLine("当属性值的OnValueChanged方法被调用,属性值为: {0}", e.NewValue); } private static object CoerceValue(DependencyObject d, object value) { Console.WriteLine("当属性值的CoerceValue方法被调用,属性值强制为: {0}", value); return value; } private static bool IsValidValue(object value) { Console.WriteLine("当属性值的IsValidValue方法被调用,对属性值进行验证,返回bool值,如果返回True表示严重通过,否则会以异常的形式抛出: {0}", value); return true; } } }

结果如下:

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

  上面简单介绍了处理流程,下面我们就以一个案例来具体看一看上面的流程到底有没有出入。

依赖属性代码文件如下:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows;

namespace WpfApp1.Controls

{

    class MyValiDP:System.Windows.Controls.Control

    {    

        //注册Current依赖属性,并添加PropertyChanged、CoerceValue、ValidateValue的回调委托
public static readonly DependencyProperty CurrentValueProperty = DependencyProperty.Register(
"CurrentValue",
typeof(double),
typeof(MyValiDP),
new FrameworkPropertyMetadata( Double.NaN, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnCurrentValueChanged), new CoerceValueCallback(CoerceCurrentValue) ), new ValidateValueCallback(IsValidValue) ); //属性包装器,通过它来暴露Current的值 public double CurrentValue { get { return (double)GetValue(CurrentValueProperty); } set { SetValue(CurrentValueProperty, value); } } //注册Min依赖属性,并添加PropertyChanged、CoerceValue、ValidateValue的回调委托 public static readonly DependencyProperty MinValueProperty = DependencyProperty.Register( "MinValue", typeof(double), typeof(MyValiDP), new FrameworkPropertyMetadata( double.NaN, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnMinValueChanged), new CoerceValueCallback(CoerceMinValue) ), new ValidateValueCallback(IsValidValue)); //属性包装器,通过它来暴露Min的值 public double MinValue { get { return (double)GetValue(MinValueProperty); } set { SetValue(MinValueProperty, value); } } //注册Max依赖属性,并添加PropertyChanged、CoerceValue、ValidateValue的回调委托 public static readonly DependencyProperty MaxValueProperty = DependencyProperty.Register( "MaxValue", typeof(double), typeof(MyValiDP), new FrameworkPropertyMetadata( double.NaN, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnMaxValueChanged), new CoerceValueCallback(CoerceMaxValue) ), new ValidateValueCallback(IsValidValue) ); //属性包装器,通过它来暴露Max的值 public double MaxValue { get { return (double)GetValue(MaxValueProperty); } set { SetValue(MaxValueProperty, value); } } //在CoerceCurrent加入强制判断赋值 private static object CoerceCurrentValue(DependencyObject d, object value) { MyValiDP g = (MyValiDP)d; double current = (double)value; if (current < g.MinValue) current = g.MinValue; if (current > g.MaxValue) current = g.MaxValue; return current; } //当Current值改变的时候,调用Min和Max的CoerceValue回调委托 private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { d.CoerceValue(MinValueProperty); d.CoerceValue(MaxValueProperty); } //当OnMin值改变的时候,调用Current和Max的CoerceValue回调委托 private static void OnMinValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { d.CoerceValue(MaxValueProperty); d.CoerceValue(CurrentValueProperty); } //在CoerceMin加入强制判断赋值 private static object CoerceMinValue(DependencyObject d, object value) { MyValiDP g = (MyValiDP)d; double min = (double)value; if (min > g.MaxValue) min = g.MaxValue; return min; } //在CoerceMax加入强制判断赋值 private static object CoerceMaxValue(DependencyObject d, object value) { MyValiDP g = (MyValiDP)d; double max = (double)value; if (max < g.MinValue) max = g.MinValue; return max; } //当Max值改变的时候,调用Min和Current的CoerceValue回调委托 private static void OnMaxValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(MinValueProperty); d.CoerceValue(CurrentValueProperty); } //验证value是否有效,如果返回True表示验证通过,否则会提示异常 public static bool IsValidValue(object value) { Double v = (Double)value; return (!v.Equals(Double.NegativeInfinity) && !v.Equals(Double.PositiveInfinity)); } } }

XAML代码如下:

<Window x:Class="WpfApp1.WindowProcess"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:local="clr-namespace:WpfApp1.Controls"

        Title="WindowProcess" Height="400" Width="500">

    <Grid>

        <StackPanel Orientation="Vertical">

            <local:MyValiDP x:Name="myValiDP1" MaxValue="500" MinValue="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=myValiDP1, Path=CurrentValue}" Height="25" VerticalAlignment="Top" />

            </StackPanel>

            <WrapPanel >

                <Label Content="最小值" />

                <Slider x:Name="sliderMin" Minimum="-200" Maximum="100" Width="300" ValueChanged="sliderMin_ValueChanged" SmallChange="10"  />

                <Label Content="{Binding ElementName=sliderMin, Path=Value}" />

            </WrapPanel>

            <WrapPanel >

                <Label Content="最大值" />

                <Slider x:Name="sliderMax" Minimum="200" Maximum="800" Width="300" ValueChanged="sliderMax_ValueChanged" SmallChange="10" />

                <Label Content="{Binding ElementName=sliderMax, Path=Value}" />

            </WrapPanel>

        </StackPanel>

    </Grid>

</Window>

C#代码如下:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Shapes; 

namespace WpfApp1
{ /// <summary>
/// WindowProcess.xaml 的交互逻辑
/// </summary> public partial class WindowProcess : Window
{ public WindowProcess()
{
InitializeComponent();
//设置Current的值
myValiDP1.CurrentValue = ; } private void sliderMin_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
//设置Current的值
myValiDP1.CurrentValue = (int)sliderMin.Value;
} private void sliderMax_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{ //设置Current的值
myValiDP1.CurrentValue = (int)sliderMax.Value; } } }

示例效果如下图。

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

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

WPF入门教程系列十四——依赖属性(四)的更多相关文章

  1. WPF入门教程系列十二——依赖属性(二)

    二. 依赖属性的优先级 由于WPF 允许我们可以在多个地方设置依赖属性的值,所以我们就必须要用一个标准来保证值的优先级别.比如下面的例子中,我们在三个地方设置了按钮的背景颜色,那么哪一个设置才会是最终 ...

  2. WPF入门教程系列十八——WPF中的数据绑定(四)

    六.排序 如果想以特定的方式对数据进行排序,可以绑定到 CollectionViewSource,而不是直接绑定到 ObjectDataProvider.CollectionViewSource 则会 ...

  3. WPF入门教程系列十九——ListView示例(一)

    经过前面的学习,今天我做一个比较综合的WPF程序示例,主要包括以下功能: 1) 查询功能.从数据库(本地数据库(local)/Test中的S_City表中读取城市信息数据,然后展示到WPF的Windo ...

  4. WPF入门教程系列十五——WPF中的数据绑定(一)

    使用Windows Presentation Foundation (WPF) 可以很方便的设计出强大的用户界面,同时 WPF提供了数据绑定功能.WPF的数据绑定跟Winform与ASP.NET中的数 ...

  5. WPF入门教程系列十六——WPF中的数据绑定(二)

    三.绑定模式 通过上一文章中的示例,学习了简单的绑定方式.在这里的示例,要学习一下绑定的模式,和模式的使用效果. 首先,我们来做一个简单示例,这个示例是根据ListBox中的选中项,去改变TextBl ...

  6. WPF入门教程系列十——布局之Border与ViewBox(五)

    九. Border Border 是一个装饰的控件,此控件绘制边框及背景,在 Border 中只能有一个子控件,若要显示多个子控件,需要将一个附加的 Panel 控件放置在父 Border 中.然后可 ...

  7. WPF入门教程系列二十三——DataGrid示例(三)

    DataGrid的选择模式 默认情况下,DataGrid 的选择模式为“全行选择”,并且可以同时选择多行(如下图所示),我们可以通过SelectionMode 和SelectionUnit 属性来修改 ...

  8. WPF入门教程系列三——Application介绍(续)

    接上文WPF入门教程系列二——Application介绍,我们继续来学习Application 三.WPF应用程序的关闭 WPF应用程序的关闭只有在应用程序的 Shutdown 方法被调用时,应用程序 ...

  9. WPF入门教程系列二——Application介绍

    一.Application介绍 WPF和WinForm 很相似, WPF与WinForm一样有一个 Application对象来进行一些全局的行为和操作,并且每个 Domain (应用程序域)中仅且只 ...

随机推荐

  1. 【12-JDBC编程】

    JDBC编程 JDBC概述 •通过使用JDBC API,Java程序可以非常方便地操作各种主流数据库,这是使Java语言的巨大 魅力所在.而且由于Java语言的跨平台特性,所以使用JDBC API所编 ...

  2. Python垃圾回收机制

    引用计数Python默认的垃圾收集机制是“引用计数”,每个对象维护了一个ob_ref字段.它的优点是机制简单,当新的引用指向该对象时,引用计数 引用计数 Python默认的垃圾收集机制是“引用计数”, ...

  3. Linux命令之route - 显示和操作IP路由表

    转自:  http://codingstandards.iteye.com/blog/1125312 用途说明 route命令用于显示和操作IP路由表(show / manipulate the IP ...

  4. angular.element的常用方法

    addClass()-为每个匹配的元素添加指定的样式类名after()-在匹配元素集合中的每个元素后面插入参数所指定的内容,作为其兄弟节点append()-在每个匹配元素里面的末尾处插入参数内容att ...

  5. 测试文档锁:doc.LockDocument()

    /// <summary> /// 总结:用到DocumentManager.Open(filePath)时,如果是ForWrite,就需要用到lock文档锁. /// </summ ...

  6. 使用ShareSDK一键分享

    首先需要到ShareSDK官方网站注册并且下载SDK,创建应用,获得ShareSDK的Appkey:文件如图: 在ShareSDK中打开即可:输入项目名称和包名,选择要分享的平台,点击确定会自动生成一 ...

  7. .net之微信企业号开发(二) 企业号人员身份认证与开发

    前言 这里完全可以链接一个登录页面,让用户输入用户名密码进行登录的...2333 但是,这样所就完全失去了微信企业号的意义,本来进入微信企业号的时候,就已经对人员身份进行认证了,你这里再让别人登录,不 ...

  8. Ubuntu 安装mysql和简单操作

    http://www.cnblogs.com/zhuyp1015/p/3561470.html ubuntu上安装mysql非常简单只需要几条命令就可以完成. 1. sudo apt-get inst ...

  9. spring 3.1 配置 JCR 303 Bean Validation

    A) 导入Hibernate-Validator  要使用JSR303 校验框架, 需要加入框架的具体实现Hibernate-Validator, 在soureforge上下载最新的Hibernate ...

  10. IOS 跳转至AppStore的两种方式

    //第一种方法 直接跳转 id+编号 [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"itms-apps: ...