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

我们通过下面的这幅图,简单介绍一下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. mysql注入读写文件

    mysql <5.0 读文件:load_file() sql-shell select load_file(''); d:/www/xx/index.php /home/webroot/.... ...

  2. mysql mHA manager 状态修改

    启动:nohup masterha_manager --conf=/etc/masterha/app1.cnf --remove_dead_master_conf --ignore_last_fail ...

  3. c++中的指针之指针在数组

    使用一维指针数组输出一维数组中的数 int array[]={1,2,3,4,5,6};        int *p; p=array;        for(int i=0;i<6;i++){ ...

  4. MVVM 在使用 ItemsSource 之前,项集合必须为空

    今天在做ListBox和Combobox绑定的时候,都出现过“在使用 ItemsSource 之前,项集合必须为空”的错误. Combobox比较简单,代码如下: <ComboBox x:Nam ...

  5. oracle 抛出自定义错误(网上找的例子)

    CREATE OR REPALCE TRIGGER minimun_age_checkBEFORE INSERT ON employeeFOR EACH ROWBEGIN IF ADD_MONTHS( ...

  6. MVC中使用Ueditor

    配置.net mvc4项目使用ueditor编辑器. 1.首先下载Ueditor1.3.6开发版(http://ueditor.baidu.com/website/download.html) 2.将 ...

  7. 更改Android Studio的主题背景

    看到别人炫酷的主题,好奇心使然,想要选择一个自己喜欢的炫酷背景,于是乎百度,动手尝试更换各种主题. 保存下来,留着以后想换主题的时候看. 1.Android Studio自带主题 File -> ...

  8. Android--网络请求

    1.Android 上发送HTTP 请求的方式一般有两种,HttpURLConnection 和 HttpClient: 2.HttpURLConnection 的用法: 1)获取 HttpURLCo ...

  9. Unity3d中Update()方法的替身

    在网上看到一些资料说Unity3d的Update方法是如何如何不好,影响性能.作为一个菜鸟,之前我还觉得挺好用的,完全没用什么影响性能的问题存在.现在发现确实有很大的问题,我习惯把一大堆检测判断放在U ...

  10. Visual Studio 2013 Nuget控制台无法找到程序包

    前几天因为公司的项目使用的是MVC4框架,用VS2015不方便新增控制器,切换回2013.用了一个大半年没有用的功能,就是Nuget控制台. 当我尝试使用Nuget控制台的命令安装一个Package的 ...