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)
{
}

以上代码为一个相对完成的依赖属性例子(还有一些功能比较少用就不写出了),从这段代码可以看出,自定义依赖属性的步骤如下:

  1. 注册依赖属性并生成依赖属性标识符。依赖属性标识符为一个public static readonly DependencyProperty字段,在上面这个例子中,依赖属性标识符为ContentProperty。依赖属性标识符的名称必须为“属性名+Property”。在PropertyMetadata中指定属性默认值。

  2. 实现属性包装器。为属性提供 CLR get 和 set 访问器,在Getter和Setter中分别调用GetValue和SetValue。Getter和Setter中不应该有其它任何自定义代码。

注意: Setter中不要写其它任何自定义代码这点很重要,如果使用Binding或其它XAML中赋值的方式,程序并不会使用Setter,而是直接调用SetValue函数赋值。这也是为什么需要使用一个PropertyChangedCallback统一处理所有值变更事件,而不是直接写在Setter里面。

  1. 如果需要监视属性值变更。可以在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:使用依赖属性的更多相关文章

  1. ASP.NET Core 依赖注入(构造函数注入,属性注入等)

    原文:ASP.NET Core 依赖注入(构造函数注入,属性注入等) 如果你不熟悉ASP.NET Core依赖注入,先阅读文章: 在ASP.NET Core中使用依赖注入   构造函数注入 构造函数注 ...

  2. SpringIOC学习_属性注入(依赖注入)

    一.应用场景:Spring会帮创建实现类的实例,但是有时候我们还需要在类中设置一些属性用于传入设置值,这些跟类紧密关联的属性就叫依赖,通过spring帮忙设置的过程叫依赖注入. 二.依赖注入的实现 A ...

  3. WPF依赖属性(续)(3)依赖属性存储

    原文:WPF依赖属性(续)(3)依赖属性存储          在之前的两篇,很多朋友参与了讨论,也说明各位对WPF/SL计数的热情,对DP系统各抒已见,当然也出现了一些分歧. 以下简称DP为依赖属性 ...

  4. WPF依赖属性(续)(2)依赖属性与附加属性的区别

    原文:WPF依赖属性(续)(2)依赖属性与附加属性的区别        接上篇,感谢各位的评论,都是认为依赖属性的设计并不是为了节省内存,从大的方面而讲是如此.样式,数据绑定,动画样样都离不开它.这篇 ...

  5. ASP.NET Core Web 应用程序系列(三)- 在ASP.NET Core中使用Autofac替换自带DI进行构造函数和属性的批量依赖注入(MVC当中应用)

    在上一章中主要和大家分享了在ASP.NET Core中如何使用Autofac替换自带DI进行构造函数的批量依赖注入,本章将和大家继续分享如何使之能够同时支持属性的批量依赖注入. 约定: 1.仓储层接口 ...

  6. 在 WPF 中获取一个依赖对象的所有依赖项属性

    原文:在 WPF 中获取一个依赖对象的所有依赖项属性 本文介绍如何在 WPF 中获取一个依赖对象的所有依赖项属性. 本文内容 通过 WPF 标记获取 通过设计器专用方法获取 通过 WPF 标记获取 p ...

  7. Java Gradle入门指南之依赖管理(添加依赖、仓库、版本冲突)

        开发任何软件,如何管理依赖是一道绕不过去的坎,软件开发过程中,我们往往会使用这样那样的第三方库,这个时候,一个好的依赖管理就显得尤为重要了.作为一个自动构建工作,Gradle对依赖管理有着很好 ...

  8. Yii2.0 依赖注入(DI)和依赖注入容器的原理

    依赖注入和依赖注入容器 为了降低代码耦合程度,提高项目的可维护性,Yii采用多许多当下最流行又相对成熟的设计模式,包括了依赖注入(Denpdency Injection, DI)和服务定位器(Serv ...

  9. VS2012生成不依赖运行时不依赖MFC的MFC程序

    转载请注明来源:http://www.cnblogs.com/xuesongshu/ 1.新建MFC或者Win32工程,全部使用默认设置 2.设置工程属性,展开配置属性,转到:常规~MFC的使用,修改 ...

  10. 项目文件中的已知 NuGet 属性(使用这些属性,创建 NuGet 包就可以不需要 nuspec 文件啦)

    知道了 csproj 文件中的一些常用 NuGet 属性,创建 NuGet 包时就可以充分发挥新 Sdk 自动生成 NuGet 包的优势,不需要 nuspec 文件啦.(毕竟 nuspec 文件没有 ...

随机推荐

  1. Frequent Distribution sorted by frequency

    import nltk def freq_sorted(text,ranklimit): fd=nltk.FreqDist(text) cumulative = 0.0 for rank, (word ...

  2. Laravel 数据插入

    Laravel 的数据库操作基于 Eloquent ORM,在插入数据时有以下几种方式,返回结果也不会不同: 1.insert 插入后会返回 true or false: 2.create 插入成功后 ...

  3. linux ssl 双向认证

    一,首先切换到apache目录下,创建一个CA文件夹 sudo mkdir CA sudo chmod 777 CA 二,然后进去CA文件夹 cp CA 三,创建其它文件 mkdir demoCA m ...

  4. LPC1768的USB使用-枚举过程

    枚举过程如下 #ifndef __USBCORE_H__ #define __USBCORE_H__ /* USB端点0 发送数据结构体*/ typedef struct _USB_EP_DATA { ...

  5. IOS数据库FMDB增、删、改、查的使用【原创】

    http://blog.it985.com/13588.html IOS数据库FMDB增.删.改.查的使用[原创] FMDB是一个XCODE的中一个轻量级的数据库,用于将网络资源存储在本地.所以,FM ...

  6. FZU 1063 三维扫描

    水题.DFS求连通块. #include<cstdio> #include<cstring> #include<cmath> #include <iomani ...

  7. Spring AOP进行日志记录,管理

    在java开发中日志的管理有很多种.我一般会使用过滤器,或者是Spring的拦截器进行日志的处理.如果是用过滤器比较简单,只要对所有的.do提交进行拦截,然后获取action的提交路径就可以获取对每个 ...

  8. IOS开发-UI学习-NSMutableAttributedString(带属性的字符串)的使用

    带属性的字符串: NSString *aa = @"hellochinaIloveYou!"; NSMutableAttributedString *mas = [[NSMutab ...

  9. eclipse安装maketplace插件

    对于默认eclipse是没有marketplace插件的,但是marketplace确实是非常好用的,可以在上面下载各种插件. 1.Help-->Install new Software 2.输 ...

  10. iOS 之 编外知识点

    iOS 使用github iOS 开源库介绍 iOS 优质方案 iOS 开发framework 后端数据库使用 Bomb方案 iOS 错误及解决汇总 后台 之 Bmob 申请苹果企业账号