五. 依赖属性回调、验证及强制值
我们通过下面的这幅图,简单介绍一下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来进行最后的验证,这就保证最后的结果是我们希望的那样了。