[UWP]为附加属性和依赖属性自定义代码段(兼容UWP和WPF)
1. 前言
之前介绍过依赖属性和附加属性的代码段,这两个代码段我用了很多年,一直都帮了我很多。不过这两个代码段我也多年没修改过,Resharper老是提示我生成的代码可以修改,它这么有诚意,这次就只好从了它,顺便简单介绍下怎么自定义代码段。
2. VisualStudio自带代码段的问题
以依赖属性为例,一个完整的依赖属性应该包含以下部分:
注册依赖属性并生成依赖属性标识符。依赖属性标识符为一个public static readonly DependencyProperty字段。依赖属性标识符的名称必须为“属性名+Property”。在PropertyMetadata中指定属性默认值。
实现属性包装器。为属性提供 get 和 set 访问器,在Getter和Setter中分别调用GetValue和SetValue。Getter和Setter中不应该有其它任何自定义代码。
如果需要监视属性值变更,可以在PropertyMetadata中定义一个PropertyChangedCallback方法。因为这个方法是静态的,可以再实现一个同名的实例方法(可以参考ContentControl的OnContentChanged方法)。
更详尽的规范可以参考《Framework Design Gidelines》。
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(ownerclass), new PropertyMetadata(0));
如上面代码所示,VisualStudio自带的依赖属性的代码段propdp只实现了最基本的功能,PropertyChangedCallback等函数还得自己实现,而这部分也挺麻烦的。另外,ownerclass基本都是当前类的名字,没有理由不使用当前类的名字作为默认值。
/// <summary>
/// 获取或设置MyProperty的值
/// </summary>
public int MyProperty
{
get => (int)GetValue(MyPropertyProperty);
set => SetValue(MyPropertyProperty, value);
}
/// <summary>
/// 标识 MyProperty 依赖属性。
/// </summary>
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(nameof(MyProperty), typeof(int), typeof(MainPage), new PropertyMetadata(default(int), OnMyPropertyChanged));
private static void OnMyPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var oldValue = (int)args.OldValue;
var newValue = (int)args.NewValue;
if (oldValue == newValue)
return;
var target = obj as MainPage;
target?.OnMyPropertyChanged(oldValue, newValue);
}
/// <summary>
/// MyProperty 属性更改时调用此方法。
/// </summary>
/// <param name="oldValue">MyProperty 属性的旧值。</param>
/// <param name="newValue">MyProperty 属性的新值。</param>
protected virtual void OnMyPropertyChanged(int oldValue, int newValue)
{
}
上面是我自定义的代码段,改进了这些地方:
- getter和setter使用了表达式主体;
- DependencyProperty.Register的第一个参数使用了
nameof()关键字代替了字符串; - typeof(MainPage)这里使用了代码段函数
ClassName()直接获取当前类的名称; - 依赖属性的默认值使用了
default()关键字,因为绝大部分情况下依赖属性的默认值就是数据类型的默认值,修改默认值的工作交给DefaultStyle的Setter; - 添加了相对完成的PropertyChangedCallback函数;
3. 如何自定义代码段
基本上,一个代码段就是一个XML文件,
3.1 代码段的结构
<?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>PropertyType</ID>
<ToolTip>属性类型</ToolTip>
<Default>int</Default>
<Function>
</Function>
</Literal>
...
</Declarations>
<Code Language="csharp" Kind="method body">
<![CDATA[ ... ]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
如上所示,代码段定义XML中主要分成以下几个部分:
- Header:包括Keyword、Shortcut等信息。Author和Description等可有可无;
- Declarations:代码段中的变量;
- Code:代码段的代码;
3.2 代码段中的变量
在我定义的依赖属性代码段中包含了三个变量:
<Literal Editable="true">
<ID>PropertyType</ID>
<ToolTip>属性类型</ToolTip>
<Default>int</Default>
<Function>
</Function>
</Literal>
<Literal Editable="true">
<ID>MyProperty</ID>
<ToolTip>属性名</ToolTip>
<Default>MyProperty</Default>
<Function>
</Function>
</Literal>
<Literal Editable="false">
<ID>classname</ID>
<ToolTip>类名</ToolTip>
<Function>ClassName()</Function>
<Default>ClassNamePlaceholder</Default>
</Literal>
其中classname不可编辑,它使用了ClassName()这个代码段函数,返回包含已插入代码段的类的名称。其它可用的代码段函数可以参考这个页面:代码段函数 。
引用变量的语法是$变量名$,如下所示:
public static readonly DependencyProperty $MyProperty$Property =
DependencyProperty.Register(nameof($MyProperty$), typeof($PropertyType$), typeof($classname$), new PropertyMetadata(default($PropertyType$), On$MyProperty$Changed));
3.3 导入代码段
在菜单上选择“工具->代码片段管理器”:

在“代码片段管理器”窗口中点击“导入”,选中需要导入的文件后打开“导入代码片段”,选择位置后点击“完成”即可完成代码段导入:

3.4 最终成果
依赖属性的代码段:
<?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>PropertyType</ID>
<ToolTip>属性类型</ToolTip>
<Default>int</Default>
<Function>
</Function>
</Literal>
<Literal Editable="true">
<ID>MyProperty</ID>
<ToolTip>属性名</ToolTip>
<Default>MyProperty</Default>
<Function>
</Function>
</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 $PropertyType$ $MyProperty$
{
get => ($PropertyType$)GetValue($MyProperty$Property);
set => SetValue($MyProperty$Property, value);
}
/// <summary>
/// 标识 $MyProperty$ 依赖属性。
/// </summary>
public static readonly DependencyProperty $MyProperty$Property =
DependencyProperty.Register(nameof($MyProperty$), typeof($PropertyType$), typeof($classname$), new PropertyMetadata(default($PropertyType$), On$MyProperty$Changed));
private static void On$MyProperty$Changed(DependencyObject obj,DependencyPropertyChangedEventArgs args)
{
var oldValue = ($PropertyType$)args.OldValue;
var newValue = ($PropertyType$)args.NewValue;
if (oldValue == newValue)
return;
var target= obj as $classname$;
target?.On$MyProperty$Changed(oldValue, newValue);
}
/// <summary>
/// $MyProperty$ 属性更改时调用此方法。
/// </summary>
/// <param name="oldValue">$MyProperty$ 属性的旧值。</param>
/// <param name="newValue">$MyProperty$ 属性的新值。</param>
protected virtual void On$MyProperty$Changed($PropertyType$ oldValue,$PropertyType$ newValue)
{
}]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
附加属性的代码段:
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Keywords>
<Keyword>ap</Keyword>
</Keywords>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
<Title>Attached Property</Title>
<Author>dino.c</Author>
<Description>For Attached Property</Description>
<HelpUrl>
</HelpUrl>
<Shortcut>ap</Shortcut>
</Header>
<Snippet>
<References>
<Reference>
<Assembly>
</Assembly>
</Reference>
</References>
<Declarations>
<Literal Editable="true">
<ID>PropertyType</ID>
<ToolTip>属性类型</ToolTip>
<Default>int</Default>
<Function>
</Function>
</Literal>
<Literal Editable="true">
<ID>MyProperty</ID>
<ToolTip>属性名</ToolTip>
<Default>MyProperty</Default>
<Function>
</Function>
</Literal>
<Literal Editable="false">
<ID>classname</ID>
<ToolTip>类名</ToolTip>
<Function>ClassName()</Function>
<Default>ClassNamePlaceholder</Default>
</Literal>
</Declarations>
<Code Language="csharp">
<![CDATA[
/// <summary>
/// 从指定元素获取 $MyProperty$ 依赖项属性的值。
/// </summary>
/// <param name="obj">从中读取属性值的元素。</param>
/// <returns>从属性存储获取的属性值。</returns>
public static $PropertyType$ Get$MyProperty$(DependencyObject obj) => ($PropertyType$)obj.GetValue($MyProperty$Property);
/// <summary>
/// 将 $MyProperty$ 依赖项属性的值设置为指定元素。
/// </summary>
/// <param name="obj">对其设置属性值的元素。</param>
/// <param name="value">要设置的值。</param>
public static void Set$MyProperty$(DependencyObject obj, $PropertyType$ value) => obj.SetValue($MyProperty$Property, value);
/// <summary>
/// 标识 $MyProperty$ 依赖项属性。
/// </summary>
public static readonly DependencyProperty $MyProperty$Property =
DependencyProperty.RegisterAttached("$MyProperty$", typeof($PropertyType$), typeof($classname$), new PropertyMetadata(default($PropertyType$), On$MyProperty$Changed));
private static void On$MyProperty$Changed(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var oldValue = ($PropertyType$)args.OldValue;
var newValue = ($PropertyType$)args.NewValue;
if (oldValue == newValue)
return;
var target = obj as $classname$;
}
]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
4. 结语
虽然这两个代码段比较复杂,并不是每次创建依赖属性都需要这么完整,但删除代码总比增加代码简单得多,所以我多年来每次创建依赖属性和附加属性都是使用这两个代码段。
WPF的依赖属性可以十分复杂,但平时用不到这么多功能,所以和UWP使用相同的代码段就够了。
完整的代码段已上传到 Github 。
5. 参考
[UWP]为附加属性和依赖属性自定义代码段(兼容UWP和WPF)的更多相关文章
- Visual Studio中自定义代码段!
Visual Studio中自定义代码段! 第一步:在编辑器中进行快捷键的输入[ctrl + shift + p] 或者 点击 查看 第一个选项就是!请看下图 第二步:选择你要配置代码段的语言, 这里 ...
- PyCharm创建自定义代码段(JetBrains系列通用)
创建自定义代码段或者修改代码段:(用习惯了VSCode的main格式,固而改下)
- VSCode创建自定义代码段
上一篇:PyCharm创建自定义代码段(JetBrains系列通用) 设置方法 很简单,快速过一下,F1,然后输入snippets 然后选择对应语言 Python案例 内容和使用: { // pref ...
- VS中自定义代码段
如果数据属性的数量比较多,那么输入总是要花费较多的时间,这里有个小技巧,就是使用快捷的输入方法,但是VS自身提供的代码段是有限的,幸运的是我们可以通过:工具> 代码段管理器>添加来添加自定 ...
- [UIKit学习]06.懒加载,模型,自定义代码段,重写构造方法
懒加载 在get中加载,且只加载一次 - (NSArray *)shops { if (_shops == nil) { NSString *file = [[NSBundle mainBundle] ...
- sublime自定义代码段
打开tools>developer>new snippet 默认代码 <snippet> <content><![CDATA[ Hello, ${1:this ...
- pycharm自定义代码段
PyCharm使用技巧:Live Templates(快速输入自定义代码片段):链接
- 在VS中自定义代码段
这个功能不怎么实用,但毕竟是VS存在的一个功能点嘛,知道一点也好!说它不怎么实用是有原因的,因为现在强大的VS编辑器拥有不计其数的插件,而且这些插件也有很多很强大的!比如Resharper,Code ...
- Xcode 自定义代码段
看见老师敲程序时,快捷键一打,所需要的一整行代码都出来了,着实感觉到效率太高了. 看了几天,才反应过来为什么自己没有get这个方法呢,现在就整理一番,记录一下. 此处以@property(nonato ...
随机推荐
- Java-- String源码分析
版权声明:本文为博主原创文章,未经博主允许不得转载 本篇博文基于java8,主要探讨java中的String源码. 首先,将一个类分为几个部分,分别是类定义(继承,实现接口等),全局变量,方法,内部类 ...
- 机器学习之隐马尔科夫模型HMM(六)
摘要 隐马尔可夫模型(Hidden Markov Model,HMM)是统计模型,它用来描述一个含有隐含未知参数的马尔科夫过程.其难点是从可观察的参数中确定该过程的隐含参数,然后利用这些参数来作进一步 ...
- zsh快捷键
bash和zsh两种shell功能非常相似,只不过zsh的功能比bash更强大一些.这两种shell的快捷键命令也非常相似,对于常用shell的人来说,了解一些快捷键操作将会十分有益,因为使用快捷键将 ...
- NVM 安装 nodejs
Windows 安装: 下载NVM 安装包:https://github.com/coreybutler/nvm-windows/releases 下载nvm-setup.zip文件后,解压后安装 安 ...
- NPOI导入导出Excel工具类
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Ref ...
- C++_调用约束
1.要求 声明定义处调用约定必须相同 int __stdcall add(int a, int b); int __stdcall add(int a, int b) { return a + b; ...
- 【大数据技术】操作系统和Hadoop版本选择
1.操作系统选择 Hadoop产品是由Java语言开发的,所以推荐的是Linux操作系统,理由很简单开源免费,推荐的操作系统CentOS. CentOS是一个基于Red Hat 企业级 Linux 提 ...
- Codeforces Round #542 B Two Cakes
B. Two Cakes time limit per test 1 second memory limit per test 256 megabytes input standard input o ...
- JZOJ 5602.【NOI2018模拟3.26】Cti
JZOJ 5602.[NOI2018模拟3.26]Cti Description 有一个 \(n×m\) 的地图,地图上的每一个位置可以是空地,炮塔或是敌人.你需要操纵炮塔消灭敌人. 对于每个炮塔都有 ...
- 机器学习算法总结(三)——集成学习(Adaboost、RandomForest)
1.集成学习概述 集成学习算法可以说是现在最火爆的机器学习算法,参加过Kaggle比赛的同学应该都领略过集成算法的强大.集成算法本身不是一个单独的机器学习算法,而是通过将基于其他的机器学习算法构建多个 ...