[UWP]依赖属性2:使用依赖属性
5. 完整的自定义依赖属性
5.1 定义
/// <summary>
/// 标识 Title 依赖属性。
/// </summary>
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(MyPage), new PropertyMetadata(string.Empty));
/// <summary>
/// 获取或设置Content的值
/// </summary>
public object Content
{
get { return (object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
/// <summary>
/// 标识 Content 依赖属性。
/// </summary>
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(object), typeof(MyPage), new PropertyMetadata(null, OnContentChanged));
private static void OnContentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
MyPage target = obj as MyPage;
object oldValue = (object)args.OldValue;
object newValue = (object)args.NewValue;
if (oldValue != newValue)
target.OnContentChanged(oldValue, newValue);
}
protected virtual void OnContentChanged(object oldValue, object newValue)
{
}
以上代码为一个相对完成的依赖属性例子(还有一些功能比较少用就不写出了),从这段代码可以看出,自定义依赖属性的步骤如下:
注册依赖属性并生成依赖属性标识符。依赖属性标识符为一个public static readonly DependencyProperty字段,在上面这个例子中,依赖属性标识符为ContentProperty。依赖属性标识符的名称必须为“属性名+Property”。在PropertyMetadata中指定属性默认值。
实现属性包装器。为属性提供 CLR get 和 set 访问器,在Getter和Setter中分别调用GetValue和SetValue。Getter和Setter中不应该有其它任何自定义代码。
注意: Setter中不要写其它任何自定义代码这点很重要,如果使用Binding或其它XAML中赋值的方式,程序并不会使用Setter,而是直接调用SetValue函数赋值。这也是为什么需要使用一个PropertyChangedCallback统一处理所有值变更事件,而不是直接写在Setter里面。
- 如果需要监视属性值变更。可以在PropertyMetadata中定义一个PropertyChangedCallback方法。因为这个方法是静态的,可以再实现一个同名的实例方法(可以参考ContentControl的OnContentChanged方法)。
5.2 代码段
注册依赖属性的语法比较难记,可以使用VisualStudio自带的代码段propdp(输入propdp后按两次tab)自动生成,这个代码段生成的代码只有基本功能,如下所示:
public int MyProperty
{
get { return (int)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(int), typeof(MyPage), new PropertyMetadata(0));
要生成完整的依赖属性代码,可以使用自定义的代码段,以下代码段生成的就是完整的依赖属性定义,快捷键是dp:
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Keywords>
<Keyword>dp</Keyword>
</Keywords>
<SnippetTypes>
<SnippetType>SurroundsWith</SnippetType>
</SnippetTypes>
<Title>Dependency Property</Title>
<Author>dino.c</Author>
<Description>For Dependency Property</Description>
<HelpUrl>
</HelpUrl>
<Shortcut>dp</Shortcut>
</Header>
<Snippet>
<References>
<Reference>
<Assembly>
</Assembly>
</Reference>
</References>
<Declarations>
<Literal Editable="true">
<ID>int</ID>
<ToolTip>int</ToolTip>
<Default>int</Default>
<Function>
</Function>
</Literal>
<Literal Editable="true">
<ID>MyProperty</ID>
<ToolTip>属性名</ToolTip>
<Default>MyProperty</Default>
<Function>
</Function>
</Literal>
<Literal>
<ID>defaultvalue</ID>
<ToolTip>默认值</ToolTip>
<Default>0</Default>
</Literal>
<Literal Editable="false">
<ID>classname</ID>
<ToolTip>类名</ToolTip>
<Function>ClassName()</Function>
<Default>ClassNamePlaceholder</Default>
</Literal>
</Declarations>
<Code Language="csharp" Kind="method body">
<![CDATA[
/// <summary>
/// 获取或设置$MyProperty$的值
/// </summary>
public $int$ $MyProperty$
{
get { return ($int$)GetValue($MyProperty$Property); }
set { SetValue($MyProperty$Property, value); }
}
/// <summary>
/// 标识 $MyProperty$ 依赖属性。
/// </summary>
public static readonly DependencyProperty $MyProperty$Property =
DependencyProperty.Register("$MyProperty$", typeof($int$), typeof($classname$), new PropertyMetadata($defaultvalue$,On$MyProperty$Changed));
private static void On$MyProperty$Changed(DependencyObject obj,DependencyPropertyChangedEventArgs args)
{
$classname$ target= obj as $classname$;
$int$ oldValue = ($int$)args.OldValue;
$int$ newValue = ($int$)args.NewValue;
if (oldValue != newValue)
target.On$MyProperty$Changed(oldValue, newValue);
}
protected virtual void On$MyProperty$Changed($int$ oldValue,$int$ newValue)
{
}]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
6. Slider与OneWayBinding的"Bug"
UWP的依赖属性比起WPF有了大幅简化,需要学习的地方少了很多,但是功简化了也不一定是一件好事。譬如下面这个代码:
<StackPanel>
<Slider x:Name="SliderSource"
Maximum="200" />
<Slider x:Name="SliderTarget"
Maximum="100"
Value="{Binding ElementName=SliderSource,Path=Value,Mode=OneWay}" />
</StackPanel>
理想的情况下,拖动SliderSource到100以后,因为SliderTarget的Value已经超过Maximum设置的100,所以没有反应;再次把SliderSource拖动到100以下,SliderTarget才会重新跟随SliderSource改变。但实际上,之后无论再怎么拖动SliderSource,SliderTarget都不会有反应。
估计所有继承自RangeBase的控件都会有这个BUG,如果要写一个RangeBase控件(包含Value,Minimum,Maximum三个double值的控件,Value必须在后两个值的范围之间),这是个很让人烦恼的问题。既然现在知道Value会被Maximum及Minimum约束,那么就可以猜想到问题出在ValueProperty的PropertyChangedCallback函数中。产生这个BUG的原因可以参考下面的代码。
private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
RangeBase range = d as RangeBase;
double minimum = range.Minimum;
double maximum = range.Maximum;
double value = (double)e.NewValue;
double initialVal = (double)e.OldValue;
if (value < minimum)
{
SetValue(ValueProperty, minimum);
}
if (value > maximum)
{
SetValue(ValueProperty, maximum);
}
range.OnValueChanged(initialVal, value);
}
实际的代码要复杂些,但基本的逻辑就是这样。如果新的Value值超过了Maximum或Minimum,就将Value重新设置为Maximum或Minimum,保证Value不会超过设定的范围。在这个例子里,如果在这个函数开头的位置调用 range.ReadLocalValue(range.ValueProperty),返回的是一个Binding,在结尾的位置调用,返回的则是double类型的100,因为这段代码将Value由OneWay Binding覆盖为maximum的double值了。
在WPF中,这个问题并不存在,因为WPF的依赖属性可以使用CoerceValueCallback约束属性值,而UWP的依赖属性被简化了,缺少这个功能。可以在网上用“Silverlight CoerceValue Helper”或“Silverlight CoerceValue Utils”等关键字试试搜索一些解决方案。为什么使用Silverlight的关键字来搜索?因为Silverlight同样存在这个问题。虽然网上能找到不少解决方案,但以我的经验来说没有方案能很好地解决这个问题。最后我的解决方案如下:
/// <summary>
/// 不要使用OneWayBinding,只能使用TwoWayBinding
/// </summary>
public object MyProperty
{
get { return (object)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
反正不是写控件库给别人用,写个注释并且和同事打声招呼就算了。
有兴趣的话可以参考Silverlight RangeBase的源代码,由于Silverlight和UWP比较接近,参考Silverlight的源码基本就可以理解RangeBase的实现细节。
RangeBase.cs
这个是Silverlight的开源实现Moonlight的源码,Moonlight的源码对理解UWP、Silverlight都很有参考价值。顺便一提,Silverlight的依赖属性参考文档也比UWP的依赖属性参考文档好用一些。
提示: 为什么使用TwoWay Binding可以解决这个问题?OneWay Binding和TwoWay Binding的内部行为不同。使用OneWay Binding的情况下,给SliderTarget.Value设置一个值,意思就只是SliderTarget的Value需要设置成一个新的值,舍弃了之前的Binding。在TwoWay Binding的情况下,设置一个值的意思不止是Value会成为那个新的值,同时绑定的对象也会更新成这个值,TwoWay Binding 理所当然地不能被舍弃。
7.参考
依赖属性概述
自定义依赖属性
Silverlight 依赖项属性概述
[UWP]依赖属性2:使用依赖属性的更多相关文章
- ASP.NET Core 依赖注入(构造函数注入,属性注入等)
原文:ASP.NET Core 依赖注入(构造函数注入,属性注入等) 如果你不熟悉ASP.NET Core依赖注入,先阅读文章: 在ASP.NET Core中使用依赖注入 构造函数注入 构造函数注 ...
- SpringIOC学习_属性注入(依赖注入)
一.应用场景:Spring会帮创建实现类的实例,但是有时候我们还需要在类中设置一些属性用于传入设置值,这些跟类紧密关联的属性就叫依赖,通过spring帮忙设置的过程叫依赖注入. 二.依赖注入的实现 A ...
- WPF依赖属性(续)(3)依赖属性存储
原文:WPF依赖属性(续)(3)依赖属性存储 在之前的两篇,很多朋友参与了讨论,也说明各位对WPF/SL计数的热情,对DP系统各抒已见,当然也出现了一些分歧. 以下简称DP为依赖属性 ...
- WPF依赖属性(续)(2)依赖属性与附加属性的区别
原文:WPF依赖属性(续)(2)依赖属性与附加属性的区别 接上篇,感谢各位的评论,都是认为依赖属性的设计并不是为了节省内存,从大的方面而讲是如此.样式,数据绑定,动画样样都离不开它.这篇 ...
- ASP.NET Core Web 应用程序系列(三)- 在ASP.NET Core中使用Autofac替换自带DI进行构造函数和属性的批量依赖注入(MVC当中应用)
在上一章中主要和大家分享了在ASP.NET Core中如何使用Autofac替换自带DI进行构造函数的批量依赖注入,本章将和大家继续分享如何使之能够同时支持属性的批量依赖注入. 约定: 1.仓储层接口 ...
- 在 WPF 中获取一个依赖对象的所有依赖项属性
原文:在 WPF 中获取一个依赖对象的所有依赖项属性 本文介绍如何在 WPF 中获取一个依赖对象的所有依赖项属性. 本文内容 通过 WPF 标记获取 通过设计器专用方法获取 通过 WPF 标记获取 p ...
- Java Gradle入门指南之依赖管理(添加依赖、仓库、版本冲突)
开发任何软件,如何管理依赖是一道绕不过去的坎,软件开发过程中,我们往往会使用这样那样的第三方库,这个时候,一个好的依赖管理就显得尤为重要了.作为一个自动构建工作,Gradle对依赖管理有着很好 ...
- Yii2.0 依赖注入(DI)和依赖注入容器的原理
依赖注入和依赖注入容器 为了降低代码耦合程度,提高项目的可维护性,Yii采用多许多当下最流行又相对成熟的设计模式,包括了依赖注入(Denpdency Injection, DI)和服务定位器(Serv ...
- VS2012生成不依赖运行时不依赖MFC的MFC程序
转载请注明来源:http://www.cnblogs.com/xuesongshu/ 1.新建MFC或者Win32工程,全部使用默认设置 2.设置工程属性,展开配置属性,转到:常规~MFC的使用,修改 ...
- 项目文件中的已知 NuGet 属性(使用这些属性,创建 NuGet 包就可以不需要 nuspec 文件啦)
知道了 csproj 文件中的一些常用 NuGet 属性,创建 NuGet 包时就可以充分发挥新 Sdk 自动生成 NuGet 包的优势,不需要 nuspec 文件啦.(毕竟 nuspec 文件没有 ...
随机推荐
- Frequent Distribution sorted by frequency
import nltk def freq_sorted(text,ranklimit): fd=nltk.FreqDist(text) cumulative = 0.0 for rank, (word ...
- Laravel 数据插入
Laravel 的数据库操作基于 Eloquent ORM,在插入数据时有以下几种方式,返回结果也不会不同: 1.insert 插入后会返回 true or false: 2.create 插入成功后 ...
- linux ssl 双向认证
一,首先切换到apache目录下,创建一个CA文件夹 sudo mkdir CA sudo chmod 777 CA 二,然后进去CA文件夹 cp CA 三,创建其它文件 mkdir demoCA m ...
- LPC1768的USB使用-枚举过程
枚举过程如下 #ifndef __USBCORE_H__ #define __USBCORE_H__ /* USB端点0 发送数据结构体*/ typedef struct _USB_EP_DATA { ...
- IOS数据库FMDB增、删、改、查的使用【原创】
http://blog.it985.com/13588.html IOS数据库FMDB增.删.改.查的使用[原创] FMDB是一个XCODE的中一个轻量级的数据库,用于将网络资源存储在本地.所以,FM ...
- FZU 1063 三维扫描
水题.DFS求连通块. #include<cstdio> #include<cstring> #include<cmath> #include <iomani ...
- Spring AOP进行日志记录,管理
在java开发中日志的管理有很多种.我一般会使用过滤器,或者是Spring的拦截器进行日志的处理.如果是用过滤器比较简单,只要对所有的.do提交进行拦截,然后获取action的提交路径就可以获取对每个 ...
- IOS开发-UI学习-NSMutableAttributedString(带属性的字符串)的使用
带属性的字符串: NSString *aa = @"hellochinaIloveYou!"; NSMutableAttributedString *mas = [[NSMutab ...
- eclipse安装maketplace插件
对于默认eclipse是没有marketplace插件的,但是marketplace确实是非常好用的,可以在上面下载各种插件. 1.Help-->Install new Software 2.输 ...
- iOS 之 编外知识点
iOS 使用github iOS 开源库介绍 iOS 优质方案 iOS 开发framework 后端数据库使用 Bomb方案 iOS 错误及解决汇总 后台 之 Bmob 申请苹果企业账号