原文:[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)
{}

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

  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里面。

  3. 如果需要监视属性值变更。可以在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 Web 应用程序系列(三)- 在ASP.NET Core中使用Autofac替换自带DI进行构造函数和属性的批量依赖注入(MVC当中应用)...

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

  2. Vue计算属性和监听属性

    一.计算属性 计算属性关键词: computed.计算属性在处理一些复杂逻辑时是很有用的. 可以看下以下反转字符串的例子: <div id="app">{{ messa ...

  3. 【IOC 控制反转】Android 视图依赖注入 ( 视图依赖注入步骤 | 视图依赖注入代码示例 )

    文章目录 总结 一.Android 视图依赖注入步骤 二.Android 布局依赖注入示例 1.创建依赖注入库 2.声明注解 3.Activity 基类 4.依赖注入工具类 5.客户端 Activit ...

  4. 【IOC 控制反转】Android 布局依赖注入 ( 布局依赖注入步骤 | 布局依赖注入代码示例 )

    文章目录 总结 一.Android 布局依赖注入步骤 二.Android 布局依赖注入示例 1.创建依赖注入库 2.声明注解 3.Activity 基类 4.依赖注入工具类 5.客户端 Activit ...

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

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

  6. vue 计算属性和data_Vue计算属性原理和使用场景

    计算属性是自动监听依赖值的变化,从而动态返回内容,监听是一个过程,在监听的值变化时,可以触发一个回调,并做一些事情.特点: 监测的是依赖值,依赖值不变的情况下其会直接读取缓存进行复用,变化的情况下才会 ...

  7. oclick vue 传参 函数_详解Vue计算属性和侦听属性

    关注[搜狐技术产品]公众号,第一时间获取技术干货 作者介绍: 本期特邀作者:浪里行舟 Github博客2600 star作者,专注于前端领域.个人公众号:「前端工匠」,致力于打造适合初中级工程师能够快 ...

  8. 关于Vue中计算属性computed和methods属性的区别,你了解多少呢

    文章目录 1.实例 2.computed计算属性中: 前提1.当计算属性fn2没有依赖data中的数据时: 前提2.:当计算属性依赖data中的数据时: 3.区别: 在做项目过程中,有时会出现同一个需 ...

  9. gradle引入依赖:_Gradle入门:依赖管理

    gradle引入依赖: 即使不是没有可能,创建没有任何外部依赖关系的现实应用程序也是一项挑战. 这就是为什么依赖性管理是每个软件项目中至关重要的部分的原因. 这篇博客文章描述了我们如何使用Gradle ...

  10. vue 计算属性和data_Vue:计算属性

    一.为什么要使用计算属性 1.什么是计算属性 计算属性:可以理解为能够在里面写一些计算逻辑的属性.具有如下的作用: 减少模板中的计算逻辑. 数据缓存.当我们的数据没有变化的时候,不会再次执行计算的过程 ...

最新文章

  1. pytorch中tensor.mul()和mm()和matmul()
  2. The Innovation | Volume 2 Issue3 正式出版
  3. 《剑指offer》整数中1出现的次数(从1到n整数中1出现的次数)
  4. C#-利用ZPL语言完毕条形码的生成和打印
  5. C/C++面试题—旋转数组的最小数字
  6. 第76节:Java中的基础知识
  7. 机器学习部分内容总结
  8. php正则表达式提取url,php 正则表达式提取图片url程序
  9. Bailian2927 判断数字个数【字符统计】
  10. 多模态关键任务与应用综述(从表示到融合,从协同学习到关键技术梳理)
  11. 一列数的规则如下: 1、1、2、3、5、8、13、21、34...... 求第30位数是多少?
  12. RS485最大通讯距离和RS485接口定义
  13. php smarty 手册下载,smarty教程
  14. 国家信息安全漏洞库最新动态发布,墨云科技成为CNNVD技术支撑单位
  15. elasticsearch安装使用
  16. 计算二叉树叶子结点数目
  17. App Store 付款方式被拒绝
  18. 体系结构:Cache Coherence
  19. “振心计划”受益房东超20万,爱彼迎中国活跃房源同比增长超两成
  20. C语言fopen函数的用法,C语言打开文件详解

热门文章

  1. logincontroller.java_ucenter整合java项目,注册问题
  2. node 升级_Node.js 版本知多少?又该如何选择?
  3. 使用双重循环,输出数字金字塔
  4. P2597 [ZJOI2012]灾难(倍增LCA+拓扑排序)
  5. 17_android下xmlpull解析
  6. HDU 5869 Different GCD Subarray Query 树状数组 + 一些数学背景
  7. 神奇的计算器dc和bc
  8. 数据查询(1)-简单查询(芮)
  9. 菜鸟学UML--概述
  10. IE6的Bug: 绝对定位遇到浮动后消失