1. 前言 之前介紹過 "依賴屬性" 和 "附加屬性" 的代碼段,這兩個代碼段我用了很多年,一直都幫了我很多。不過這兩個代碼段我也多年沒修改過,Resharper老是提示我生成的代碼可以修改,它這麼有誠意,這次就只好從了它,順便簡單介紹下怎麼自定義代碼段。 2. VisualStudio自帶代碼段的 ...
##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. 參考 代碼段