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

我们通过下面的这幅图,简单介绍一下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. android技巧总结

    技巧1. 在写布局文件时,有时不需要给控件指定text值,但是又想知道他的位置是否是自己想要他在的位置.这种情况只有在运行时给他指定text值才能确切地知道它显示的位置. 现在有一种方法可以实现,即利 ...

  2. 解决 IE 6/7 中console对象兼容性问题

    话不多说,直接上代码 (function (){ //创建空console对象,避免JS报错 if(!window.console) window.console = {}; var console ...

  3. Java读取文件的几种方式

    package com.mesopotamia.test; import java.io.BufferedReader; import java.io.ByteArrayInputStream; im ...

  4. 在Xcode 6 beta里编译Cocos2d-x iOS项目时失败

    转载 在Xcode 6 beta里编译Cocos2d-x iOS项目时可能会失败,提示如下错误: Undefined symbols for architecture i386: "_fwr ...

  5. HTML和XHTML的一点事儿.

    什么是 HTML? HTML 是用来描述网页的一种语言. HTML 指的是超文本标记语言 (Hyper Text Markup Language) HTML 不是一种编程语言,而是一种标记语言 (ma ...

  6. MyEclipse 2015 Stable 2.0安装包及破解工具下载

    MyEclipse 2015 Stable 2.0安装包及破解文件下载 之前一直在用myeclipse10.7,后来发现10.7版本有点老了,所以就换了2015稳定版的myeclipse,里面附带了破 ...

  7. HDOJ 4749 Parade Show

    说实在的在比赛时看错了题意,一直对最后一段的描述不是很清楚.闲话少说: 题意:给一个主串,再一个副串,问主串中有多少个子串和副串的的规律相同,即相邻的相等大于小于,用过的就不能再用了. #includ ...

  8. zTree的功能解析

    zTree ,一个依靠 jQuery 实现的多功能 "树插件".优异的性能.灵活的配置.多种功能的组合是 zTree 最大优点.兼容 IE.FireFox.Chrome 等浏览器, ...

  9. 为什么要放弃使用Thread.Sleep

    前言 此文并不是说要完全放弃使用Thread.Sleep,而是要说明在符合哪些情况下使用! 场景 很多时候,我们会需要一个定时服务来处理业务. 但并不是死死的每隔N分钟执行一次那种,而是在一次处理完后 ...

  10. 【T-SQL基础】01.单表查询-几道sql查询题

    概述: 本系列[T-SQL基础]主要是针对T-SQL基础的总结. [T-SQL基础]01.单表查询-几道sql查询题 [T-SQL基础]02.联接查询 [T-SQL基础]03.子查询 [T-SQL基础 ...