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

我们通过下面的这幅图,简单介绍一下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 = 1;} }}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(0,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 = 100;}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主要是验证该数据的有效性,最设置了值以后都会调用它来进行验证,如果验证不成功,则抛出异常。

转载于:https://www.cnblogs.com/chillsrc/p/4688983.html

WPF入门教程系列十四——依赖属性(四)相关推荐

  1. WPF入门教程(七)---依赖属性(3)(转)

    WPF入门教程(七)---依赖属性(3) 2018年08月24日 08:33:43 weixin_38029882 阅读数:50 四. 只读依赖属性 在以前在对于非WPF的功能来说,对于类的属性的封装 ...

  2. WPF入门教程(八)--依赖属性(4)(转)

    WPF入门教程(八)--依赖属性(4) 2018年08月27日 11:35:55 weixin_38029882 阅读数:71 我们通过下面的这幅图,简单介绍一下WPF属性系统对依赖属性操作的基本步骤 ...

  3. WPF入门教程(八)--依赖属性(4)

    我们通过下面的这幅图,简单介绍一下WPF属性系统对依赖属性操作的基本步骤: 借用一个常见的图例,介绍一下WPF属性系统对依赖属性操作的基本步骤: 第一步,确定Base Value,对同一个属性的赋值可 ...

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

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

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

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

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

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

  7. WPF入门教程系列四——Dispatcher介绍

    WPF入门教程系列四--Dispatcher介绍 一.Dispatcher介绍 微软在WPF引入了Dispatcher,那么这个Dispatcher的主要作用是什么呢? 不管是WinForm应用程序还 ...

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

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

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

    四. 只读依赖属性 在以前在对于非WPF的功能来说,对于类的属性的封装中,经常会对那些希望暴露给外界只读操作的字段封装成只读属性,同样在WPF中也提供了只读属性的概念,如一些 WPF控件的依赖属性是只 ...

最新文章

  1. 互联网大脑的发育与元宇宙的兴起
  2. GetProcAddress 根据 ordinal 导入函数
  3. 一个java文件里面可以写多少个class
  4. Kaggle新上比赛:空客公司卫星图像船体分割
  5. MYSQL数据库学习----查询
  6. python建立数据库连接时出错_python – 尝试连接到localhost上的数据库时出现pyodbc连接错误...
  7. 苹果Mac照片编辑插件套件:Nik Collection
  8. ESP8266 教程1 — ESP8266硬件平台介绍
  9. 大话IT第十期:由Windows 8引发的Wintel内讧
  10. 新浪微博用户兴趣建模系统架构
  11. 2022东南大学网安916考研经验贴
  12. 用Moment.js 计算两个时间直接的间隔
  13. 十六种顶级的思维模型
  14. 数仓工具—Hive进阶之数据存储格式(5)
  15. 保健品不是药为什么能“治病”?
  16. 二氧化碳浓度数据集整理
  17. sql选取连续三天登录的用户
  18. access2003数据库连接
  19. Win11游戏性能设置 提高游戏性能方法
  20. 程序员就该这样解读《隐秘的角落》

热门文章

  1. 【ES6(2015)】Map
  2. uniapp 支付(支付宝,微信支付)
  3. mui 头部tab代码
  4. python的ctypes模块详解数组_python ctypes结构数组
  5. SQLyog笔记-CURRENT_TIMESTAMP在SQLyog的配置
  6. C++笔记-ClassA a和ClassA a()的区别与联系
  7. C++ STL string修改
  8. Java高级语法笔记-自定义异常类
  9. css3书页翻转,CSS3实现3D翻书效果
  10. java ftp批量下载_java ftp连接一次下载多个文件