• [UWP]为附加属性和依赖属性自定义代码段(兼容UWP和WPF)


    1. 前言

    之前介绍过依赖属性附加属性的代码段,这两个代码段我用了很多年,一直都帮了我很多。不过这两个代码段我也多年没修改过,Resharper老是提示我生成的代码可以修改,它这么有诚意,这次就只好从了它,顺便简单介绍下怎么自定义代码段。

    2. VisualStudio自带代码段的问题

    以依赖属性为例,一个完整的依赖属性应该包含以下部分:

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

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

    3. 如果需要监视属性值变更,可以在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中主要分成以下几个部分:

    1. Header:包括Keyword、Shortcut等信息。Author和Description等可有可无;
    2. Declarations:代码段中的变量;
    3. 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. 参考

    代码段

  • 相关阅读:
    正经学C#_循环[do while,while,for]:[c#入门经典]
    Vs 控件错位 右侧资源管理器文件夹点击也不管用,显示异常
    asp.net core 获取当前请求的url
    在实体对象中访问导航属性里的属性值出现异常“There is already an open DataReader associated with this Command which must be
    用orchard core和asp.net core 3.0 快速搭建博客,解决iis 部署https无法登录后台问题
    System.Data.Entity.Core.EntityCommandExecution The data reader is incompatible with the specified
    初探Java设计模式3:行为型模式(策略,观察者等)
    MySQL教程77-CROSS JOIN 交叉连接
    MySQL教程76-HAVING 过滤分组
    MySQL教程75-使用GROUP BY分组查询
  • 原文地址:https://www.cnblogs.com/dino623/p/Snippet.html
Copyright © 2020-2023  润新知