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 = ;
}
}
}
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入门教程系列十四——依赖属性(四)的更多相关文章
- WPF入门教程系列十二——依赖属性(二)
二. 依赖属性的优先级 由于WPF 允许我们可以在多个地方设置依赖属性的值,所以我们就必须要用一个标准来保证值的优先级别.比如下面的例子中,我们在三个地方设置了按钮的背景颜色,那么哪一个设置才会是最终 ...
- WPF入门教程系列十八——WPF中的数据绑定(四)
六.排序 如果想以特定的方式对数据进行排序,可以绑定到 CollectionViewSource,而不是直接绑定到 ObjectDataProvider.CollectionViewSource 则会 ...
- WPF入门教程系列十九——ListView示例(一)
经过前面的学习,今天我做一个比较综合的WPF程序示例,主要包括以下功能: 1) 查询功能.从数据库(本地数据库(local)/Test中的S_City表中读取城市信息数据,然后展示到WPF的Windo ...
- WPF入门教程系列十五——WPF中的数据绑定(一)
使用Windows Presentation Foundation (WPF) 可以很方便的设计出强大的用户界面,同时 WPF提供了数据绑定功能.WPF的数据绑定跟Winform与ASP.NET中的数 ...
- WPF入门教程系列十六——WPF中的数据绑定(二)
三.绑定模式 通过上一文章中的示例,学习了简单的绑定方式.在这里的示例,要学习一下绑定的模式,和模式的使用效果. 首先,我们来做一个简单示例,这个示例是根据ListBox中的选中项,去改变TextBl ...
- WPF入门教程系列十——布局之Border与ViewBox(五)
九. Border Border 是一个装饰的控件,此控件绘制边框及背景,在 Border 中只能有一个子控件,若要显示多个子控件,需要将一个附加的 Panel 控件放置在父 Border 中.然后可 ...
- WPF入门教程系列二十三——DataGrid示例(三)
DataGrid的选择模式 默认情况下,DataGrid 的选择模式为“全行选择”,并且可以同时选择多行(如下图所示),我们可以通过SelectionMode 和SelectionUnit 属性来修改 ...
- WPF入门教程系列三——Application介绍(续)
接上文WPF入门教程系列二——Application介绍,我们继续来学习Application 三.WPF应用程序的关闭 WPF应用程序的关闭只有在应用程序的 Shutdown 方法被调用时,应用程序 ...
- WPF入门教程系列二——Application介绍
一.Application介绍 WPF和WinForm 很相似, WPF与WinForm一样有一个 Application对象来进行一些全局的行为和操作,并且每个 Domain (应用程序域)中仅且只 ...
随机推荐
- node的事件模块应用(译)
第一次接触Node.js时,就觉得他只不过是用javascript实现的服务端.但实际上他提供了许多浏览器端不具备的方法,比如EventEmitter类.我们在本文中来学习如何使用EventEmitt ...
- python基础整理笔记(八)
一. python反射的方式来调用方法属性 反射主要指的就是hasattr.getattr.setattr.delattr这四个函数,作用分别是检查是否含有某成员.获取成员.设置成员.删除成员. 此外 ...
- middleware - bodyparser
express4之前,bodyparser是express下的一个对象. express4把bodyparser分离出来. 本文中的实例基于以下的这个请求 $.ajax({ url: '/save', ...
- 如何给澳洲路局写信refound罚金,遇到交通罚款怎么办
在澳洲,100%的司机收到过罚单,包括停车,超速,闯红灯等等,其罚金一般都在200-500之间,当然其单位是AUD.所以,对大多数留学生来说,收到罚金意味着一个礼拜要吃吐了. 本人就收到过一次超速罚单 ...
- XPath使用示例
1.查找空节点//*[not(text())] 表示内容为空的节点//*[count(*)=0] 表示没有子节点的节点"//*[count(*)=0 and n ...
- [转]c++中vector的使用
C++中的vector使用范例 一.概述 vector是C++标准模板库中的部分内容,它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库.vector是一个容器,它能够存放各种类型的对象,简 ...
- NGUI 3.0.7的新锚点系统设置不好就会造成显示错误的错觉
每次设置NGUI控件的锚点时,都需要刷新一下窗口,不然就会造成显示错误的错觉. 同时,NGUI控件设置锚点的参考对象为一个物体时还需要调整其控件大小,不然也会造成显示错误的错觉.
- maven 简介
本书代码下载 大家可以从我的网站下载本书的代码:http://www.juvenxu.com/mvn-in-action/,也可以通过我的网站与我取得联系,欢迎大家与我交流任何关于本书的问题和关于Ma ...
- bzoj 3718
题意:戳这里 思路:很容易发现对于一个车能否移动到最终的位置只要判断路径中得最大高度与自身高端之和是否>w即可. 那么就可以转化为逆序对得最大数问题..即对于每一辆车,判断有那些最初在他前面,而 ...
- JavaScript知识总结<一>
JavaScript核心基础语法: 1.什么是JavaScript? 我们知道在Web标准中网页由:结构.形式.行为三部分组成:结构由标准形式XHTML.形式又标准形式CSS,那么行为的表现就由Jav ...