DependencyProperty.UnsetValue 的正确打开方式
无论是 WPF,还是 UWP,只要你用了绑定或者标记扩展,一定会碰到一个神奇的值——DependencyProperty.UnsetValue。UnsetValue 是什么意思?为什么会出现这个值呢?如果要让 UnsetValue 为我们所用,正确的用法又是什么呢?
DependencyProperty.UnsetValue 是什么?
要知道这是什么,一定要看源码:
/// <summary> Standard unset value </summary>
public static readonly object UnsetValue = new NamedObject("DependencyProperty.UnsetValue");
这是一个 NamedObject,而 NamedObject 又是什么呢?
internal class NamedObject
{
public NamedObject(string name)
{
if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(name);
_name = name;
}
public override string ToString()
{
if (_name[0] != '{') _name = String.Format(CultureInfo.InvariantCulture, "{{{0}}}", _name);
return _name;
}
string _name;
}
好吧,其实这个类根本就没有什么用途,微软只是随便找了一个类,以便你在 Visual Studio 调试器或者你自己用代码输出值的时候能够显示一个预设好的字符串。真的只是起调试作用的啊!
在 DependencyProperty.UnsetValue 的定义中,只是为了让大家调试的时候显示 DependencyProperty.UnsetValue 而已。值本身不代表任何意义,只是为了说明遇到了一个“未设置”的值。
但是有人会问:null 在调试的时候也会显示 null 啊,为啥不用 null,要特别准备一个值呢?
这是因为在绑定中,null 可能是一个合理的值,可能会被故意用在绑定中来达到某种目的。于是微软必须用一个大家平常开发中一定不会用到的值来表示“不合理”,于是祭出了 DependencyProperty.UnsetValue。
什么情况下会出现 DependencyProperty.UnsetValue?
正常情况下,只有以下两处代码会遇到 DependencyProperty.UnsetValue:
- 在用于绑定的转换器
IValueConverterIMultiValueConverter的代码里面; - 在 XAML 标记扩展
MarkupExtension里面。
而以上两处代码,只有在发生以下三种情况时才会遇到 DependencyProperty.UnsetValue:
- 绑定出现了错误,也就是说绑定从最开始的源值到目标值的若干次转换过程中任何阶段发生了错误以至于无法成功转换到目标值。
虽然我们写的是一个{Binding XXX},但XXX可能由另外的绑定来提供(例如逻辑父控件的 DataContext)。一次次绑定的源值是上一个绑定的目标值,于是这样的关系组合成一个绑定提供值的链条。链条中只要有一处不能提供合理的值,就会在绑定中得到UnsetValue。 - 绑定或者标记扩展写在了
ControlTemplate或者DataTemplate里面,但此时并没有指定数据源。
在模板应用到实际的控件之前,模板本身也会执行一次Binding和MarkupExtension的逻辑。于是如果绑定需要依赖于实际的控件,那么实际上Binding和MarkupExtension会至少执行两次,其中第一次便是模板中的那一次。此时获取依赖属性的值时拿到的便是DependencyProperty.UnsetValue。 - 使用依赖项属性的
ReadLocalValue来获取值,而不是GetValue;但此时并没有为依赖对象设置值。
如果没有设置值,那么GetValue会返回更低优先级的值,一般情况下是依赖项属性在注册时的默认值;但ReadLocalValue就是在获取显式设置的那个值,如果没设,就只能是DependencyProperty.UnsetValue了。
我们应该如何正确使用 DependencyProperty.UnsetValue?
微软官方对于 DependencyProperty.UnsetValue 的介绍,专门的文档中只有一个说法,就是用来表示“不合理”的值,却并没有说明什么情况下为合理,什么情况下为不合理。但好在微软将一些推荐写法散落在了多个不同的文章中。这里整理在一起,以便为大家对 DependencyProperty.UnsetValue 的正确使用提供指导。
- 在注册依赖项属性的时候,不要使用
DependencyProperty.UnsetValue作为默认值。
这个值本意其实并不是在说“未设置”,而是代表“不合理”。默认值必须是“合理地”才行。微软官方文档 Custom dependency properties 对此的解释是,如果默认值设置为UnsetValue,则会在大家使用其值的时候产生混淆,并不能区分到底是依赖属性(的绑定系统)提供值的时候出错了还是因为只是默认没设置。 - 在写绑定的转换器的时候,如果转换有错误,不应该抛出异常,而是应该返回一个
DependencyProperty.UnsetValue,以便阻止绑定中继续传递值。
微软在 Data binding in depth 中写出了这个要求,而在 How to: Convert Bound Data 中给出了示例代码。 - 如果需要在
CoerceValueCallback回调中验证值的合理性,当值不合理的时候,返回DependencyProperty.UnsetValue。
这将告诉依赖属性系统阻止这次值的更改。
参考资料
- Data binding in depth - UWP app developer - Microsoft Docs
- How to: Convert Bound Data - Microsoft Docs
- Custom dependency properties - UWP app developer - Microsoft Docs
- Dependency Property Callbacks and Validation - Microsoft Docs
- c# - Why do I get a DependencyProperty.UnsetValue when converting a value in a MultiBinding? - Stack Overflow
- DependencyProperty.UnsetValue Field (System.Windows)
- UnsetValue
DependencyProperty.UnsetValue 的正确打开方式的更多相关文章
- iOS开发小技巧--相机相册的正确打开方式
iOS相机相册的正确打开方式- UIImagePickerController 通过指定sourceType来实现打开相册还是相机 UIImagePickerControllerSourceTypeP ...
- Xcode 的正确打开方式——Debugging(转载)
Xcode 的正确打开方式——Debugging 程序员日常开发中有大量时间都会花费在 debug 上,从事 iOS 开发不可避免地需要使用 Xcode.这篇博客就主要介绍了 Xcode 中几种能 ...
- C#语法——泛型的多种应用 C#语法——await与async的正确打开方式 C#线程安全使用(五) C#语法——元组类型 好好耕耘 redis和memcached的区别
C#语法——泛型的多种应用 本篇文章主要介绍泛型的应用. 泛型是.NET Framework 2.0 版类库就已经提供的语法,主要用于提高代码的可重用性.类型安全性和效率. 泛型的定义 下面定义了 ...
- InnoDB缓冲池预加载在MySQL 5.7中的正确打开方式
InnoDB缓冲池预加载在MySQL 5.7中的正确打开方式 https://mp.weixin.qq.com/s/HGa_90XvC22anabiBF8AbQ 在这篇文章里,我将讨论在MySQL 5 ...
- Console控制台的正确打开方式
Console控制台的正确打开方式 console对象提供了访问浏览器调试模式的信息到控制台 -- Console对象 |-- assert() 如果第一个参数断言为false,则在控制台输出错误信息 ...
- 任务队列和异步接口的正确打开方式(.NET Core版本)
任务队列和异步接口的正确打开方式 什么是异步接口? Asynchronous Operations Certain types of operations might require processi ...
- (一)Redis for Windows正确打开方式
目录 (一)Redis for Windows正确打开方式 (二)Redis for 阿里云公网连接 (三)Redis for StackExchange.Redis 下载地址 官网.中文网1 及 中 ...
- List的remove()方法的三种正确打开方式
转: java编程:List的remove()方法的三种正确打开方式! 2018年08月12日 16:26:13 Aries9986 阅读数 2728更多 分类专栏: leetcode刷题 版权声 ...
- C++11随机数的正确打开方式
C++11随机数的正确打开方式 在C++11之前,现有的随机数函数都存在一个问题:在利用循环多次获取随机数时,如果程序运行过快或者使用了多线程等方法,srand((unsigned)time(null ...
随机推荐
- PHP中用下划线开头的含义
命名的规则 加一个为私有的 加两个一般都是系统默认的,系统预定义的,即所谓:=====================“魔术方法”与“魔术常量”=====================★PHP起止为 ...
- R-模式(mode)和类(class)
数据模式:mode函数显示任何对象的模式.常见的单个的对象模式是逻辑型(Logical).数值型(Numeric).字符型(Character). 常用到的数据模式是列表(list). 逻辑型:TRU ...
- JS触发服务器控件的单击事件
<script src="../Js/jquery-1.4.2.min.js" type="text/javascript"></script ...
- List和数组的相互转化
一.数组转化为list:Arrays.aslist(arr); public static void main(String[] args) { String[] arr={"apple&q ...
- Git 从了解到放弃
1. 简单介绍 1.1. git起源 在1991年linus创建了Linux从此linux成为服务器领域的佼佼者,大部分web服务器.邮件.数据库各种服务器端程序都安装在了linux上面运行,主要是因 ...
- ParentNodes、childNodes、children之间的区别
"parentNode" 常用来获取某个元素的父节点. 把 parentNodes 理解为容器, 在容器中有个子节点 例: <div id="parent" ...
- LeetCode OJ : Different Ways to Add Parentheses(在不同位置增加括号的方法)
Given a string of numbers and operators, return all possible results from computing all the differen ...
- IOC与DI简介
IOC:控制反转(Inverse Of Control) 在没用spring框架之前我们是这样写程序的: private UserDao userDao = new UserDaoImpl(); 也就 ...
- 如何修改MyEclipse的默认编码方式
在创建jsp页面时,默认首行出现“<%@ page language="java" import="java.util.*" pageEncoding=& ...
- Redis的集群安装以及rehash重新迁移教程指南
1. Redis的cluster集群 在官方文档Cluster Spec中,作者详细介绍了Redis集群为什么要设计成现在的样子.最核心的目标有三个: 性能:这是Redis赖以生存的看家本领,增加集群 ...