WPF基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)
[2] WPF基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)
[3] WPF基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)
[4] WPF基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)
[5] WPF基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)
[6] WPF基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)
十二. 依赖属性回调、验证及强制值
我们通过下面的这幅图,简单介绍一下WPF属性系统对依赖属性操作的基本步骤:
- 第一步,基础值就是上面“六.依赖属性的优先级”提供的那些显示设置,所以它的优先级比较好确定,但有些不会按常规出牌,所以也需要注意总结。
- 第二步,如果依赖属性值是计算表达式 (如前面示例中的绑定等语法特性),这个时候就会计算表达式的结果作为第二步的值。
- 第三步,动画是一种优先级很高的特殊行为,很多时候,我们都会听到动画优先的声音,所以它的优先级高于其他基础设置;
- 第四步,强制则是注册时提供的 CoerceValueCallback 委托,它负责验证属性值是否在允许的限制范围之内,和我们对属性的验证一样,比如强制设置该值必须大于于0小于10等等;
- 第五步,验证是指我们注册依赖属性所提供的 ValidateValueCallback 委托方法,它最终决定了属性值设置是否有效,当数据无效时会抛出异常来通知。
前面我们讲了基本的流程,下面我们就用一个小的例子来进行说明:
namespace SampleProcess_DPs
{
class Program
{
static void Main(string[] args)
{
SimpleDPClass sDPClass = new SimpleDPClass();
sDPClass.SimpleDP = 8;
Console.ReadLine();
}
}
public class SimpleDPClass : DependencyObject
{
public static readonly DependencyProperty SimpleDPProperty =
DependencyProperty.Register("SimpleDP", typeof(double), typeof(SimpleDPClass),
new FrameworkPropertyMetadata((double)0.0,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(OnValueChanged),
new CoerceValueCallback(CoerceValue)),
new ValidateValueCallback(IsValidValue));
public double SimpleDP
{
get { return (double)GetValue(SimpleDPProperty); }
set { SetValue(SimpleDPProperty, value); }
}
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Console.WriteLine("当值改变时,我们可以做的一些操作,具体可以在这里定义: {0}", e.NewValue);
}
private static object CoerceValue(DependencyObject d, object value)
{
Console.WriteLine("对值进行限定,强制值: {0}", value);
return value;
}
private static bool IsValidValue(object value)
{
Console.WriteLine("验证值是否通过,返回bool值,如果返回True表示严重通过,否则会以异常的形式暴露: {0}", value);
return true;
}
}
}
结果如下:
当SimpleDP属性变化之后,PropertyChangeCallback就会被调用。可以看到结果并没有完全按照我们先前的流程先Coerce后Validate的顺序执行,有可能是WPF内部做了什么特殊处理,当属性被修改时,首先会调用Validate来判断传入的value是否有效,如果无效就不继续后续的操作,这样可以更好的优化性能。从上面的结果上看出,CoerceValue后面并没有立即ValidateValue,而是直接调用了PropertyChanged。这是因为前面已经验证过了value,如果在Coerce中没有改变value,那么就不用再验证了。如果在 Coerce中改变了value,那么这里还会再次调用ValidateValue操作,和前面的流程图执行的顺序一样,在最后我们会调用ValidateValue来进行最后的验证,这就保证最后的结果是我们希望的那样了(正如打游戏一样,打了小怪,在最后过总关的时候还是需要打大怪才能闯关的)。
上面简单介绍了处理流程,下面我们就以一个案例来具体看一看上面的流程到底有没有出入,这个例子改编于Sacha Barber 的Dependency Properties代码示例,我相信通过这段代码你会对这个上面讲的概念有更清晰地认识。
UI很简单,黄色部分显示当前值,我们在初始化的时候把它设置为100,然后它的最小值和最大值分别设置为0和500,按钮”设置为-100“企图把当前值设为-100,按钮”设置为1000“试图把当前值设为1000。具体大家看代码(我都写了注释,很容易理解的).
依赖属性代码文件如下:
namespace Callback_Validation_DPs
{
public class Gauge : Control
{
public Gauge() : base() { }
//注册CurrentReading依赖属性,并添加PropertyChanged、CoerceValue、ValidateValue的回调委托
public static readonly DependencyProperty CurrentReadingProperty = DependencyProperty.Register(
"CurrentReading",
typeof(double),
typeof(Gauge),
new FrameworkPropertyMetadata(
Double.NaN,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(OnCurrentReadingChanged),
new CoerceValueCallback(CoerceCurrentReading)
),
new ValidateValueCallback(IsValidReading)
);
//属性包装器,通过它来暴露CurrentReading的值
public double CurrentReading
{
get { return (double)GetValue(CurrentReadingProperty); }
set { SetValue(CurrentReadingProperty, value); }
}
//注册MinReading依赖属性,并添加PropertyChanged、CoerceValue、ValidateValue的回调委托
public static readonly DependencyProperty MinReadingProperty = DependencyProperty.Register(
"MinReading",
typeof(double),
typeof(Gauge),
new FrameworkPropertyMetadata(
double.NaN,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(OnMinReadingChanged),
new CoerceValueCallback(CoerceMinReading)
),
new ValidateValueCallback(IsValidReading));
//属性包装器,通过它来暴露MinReading的值
public double MinReading
{
get { return (double)GetValue(MinReadingProperty); }
set { SetValue(MinReadingProperty, value); }
}
//注册MaxReading依赖属性,并添加PropertyChanged、CoerceValue、ValidateValue的回调委托
public static readonly DependencyProperty MaxReadingProperty = DependencyProperty.Register(
"MaxReading",
typeof(double),
typeof(Gauge),
new FrameworkPropertyMetadata(
double.NaN,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(OnMaxReadingChanged),
new CoerceValueCallback(CoerceMaxReading)
),
new ValidateValueCallback(IsValidReading)
);
//属性包装器,通过它来暴露MaxReading的值
public double MaxReading
{
get { return (double)GetValue(MaxReadingProperty); }
set { SetValue(MaxReadingProperty, value); }
}
//在CoerceCurrentReading加入强制判断赋值
private static object CoerceCurrentReading(DependencyObject d, object value)
{
Gauge g = (Gauge)d;
double current = (double)value;
if (current < g.MinReading) current = g.MinReading;
if (current > g.MaxReading) current = g.MaxReading;
return current;
}
//当CurrentReading值改变的时候,调用MinReading和MaxReading的CoerceValue回调委托
private static void OnCurrentReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(MinReadingProperty);
d.CoerceValue(MaxReadingProperty);
}
//当OnMinReading值改变的时候,调用CurrentReading和MaxReading的CoerceValue回调委托
private static void OnMinReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(MaxReadingProperty);
d.CoerceValue(CurrentReadingProperty);
}
//在CoerceMinReading加入强制判断赋值
private static object CoerceMinReading(DependencyObject d, object value)
{
Gauge g = (Gauge)d;
double min = (double)value;
if (min > g.MaxReading) min = g.MaxReading;
return min;
}
//在CoerceMaxReading加入强制判断赋值
private static object CoerceMaxReading(DependencyObject d, object value)
{
Gauge g = (Gauge)d;
double max = (double)value;
if (max < g.MinReading) max = g.MinReading;
return max;
}
//当MaxReading值改变的时候,调用MinReading和CurrentReading的CoerceValue回调委托
private static void OnMaxReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(MinReadingProperty);
d.CoerceValue(CurrentReadingProperty);
}
//验证value是否有效,如果返回True表示验证通过,否则会提示异常
public static bool IsValidReading(object value)
{
Double v = (Double)value;
return (!v.Equals(Double.NegativeInfinity) && !v.Equals(Double.PositiveInfinity));
}
}
}
XAML代码如下:
<Window x:Class="Callback_Validation_DPs.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Callback_Validation_DPs"
WindowStartupLocation="CenterScreen"
Title="Callback_Validation_DPs" Height="400" Width="400">
<StackPanel Orientation="Vertical">
<local:Gauge x:Name="gauge1" MaxReading="100" MinReading="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=gauge1, Path=CurrentReading}" Height="25" VerticalAlignment="Top" />
</StackPanel>
<Button x:Name="btnSetBelowMin" Content="设置为 -100"
Click="btnSetBelowMin_Click"/>
<Button x:Name="btnSetAboveMax" Content="设置为 1000"
Click="btnSetAboveMax_Click"/>
</StackPanel>
</Window>
XAML的后台代码如下:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
//设置CurrentReading的值,这个时候会触发哪些变化?调试代码吧!
gauge1.CurrentReading = 100;
}
private void btnSetBelowMin_Click(object sender, RoutedEventArgs e)
{
//设置CurrentReading的值,这个时候会触发哪些变化?调试代码吧!
gauge1.CurrentReading = -100;
}
private void btnSetAboveMax_Click(object sender, RoutedEventArgs e)
{
//设置CurrentReading的值,这个时候会触发哪些变化?调试代码吧!
gauge1.CurrentReading = 10000;
}
}
在上面的例子中,一共有三个依赖属性相互作用——CurrentReading、MinReading和MaxReading,这些属性相互作用,但它们的规则是MinReading≤CurrentReading≤MaxReading。根据这个规则,当其中一个依赖属性变化时,另外两个依赖属性必须进行适当的调整,这里我们要用到的就是CoerceValue这个回调委托,那么实现起来也非常的简单,注册MaxReading的时候加入CoerceValueCallback,在CoerceMaxReading函数中做处理:如果Maximum的值小于MinReading,则使MaxReading值等于MinReading;同理在CurrentReading中也加入了CoerceValueCallback进行相应的强制处理。然后在MinReading的ChangedValueCallback被调用的时候,调用CurrentReading和MaxReading的CoerceValue回调委托,这样就可以达到相互作用的依赖属性一变应万变的”千机变“。
换句话说,当相互作用的几个依赖属性其中一个发生变化时,在它的PropertyChangeCallback中调用受它影响的依赖属性的CoerceValue,这样才能保证相互作用关系的正确性。 前面也提高ValidateValue主要是验证该数据的有效性,最设置了值以后都会调用它来进行验证,如果验证不成功,则抛出异常。