[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. 參考

代碼段


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM