• WPF入门教程系列十四——依赖属性(四)


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

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

     

  • 相关阅读:
    hdu1828线段树(两次扫描+离散化)
    hdu1542线段树(扫描线+离散化)
    二分思想
    hdu2871线段树区间更新
    git初体验
    python笔记-模块
    python笔记-动态类型
    AWS上创建EKS(K8S)集群
    Akamai CDN刷新(通过Akamai cli 自动刷新)
    创建Akamai cdn api授权
  • 原文地址:https://www.cnblogs.com/chillsrc/p/4688983.html
Copyright © 2020-2023  润新知