[UWP]依赖属性2:使用依赖属性
5. 完整的自定義依賴屬性
5.1 定義
/// <summary> /// 標(biāo)識 Title 依賴屬性。 /// </summary> public static readonly DependencyProperty TitleProperty =DependencyProperty.Register("Title", typeof(string), typeof(MyPage), new PropertyMetadata(string.Empty));/// <summary> /// 獲取或設(shè)置Content的值 /// </summary> public object Content {get { return (object)GetValue(ContentProperty); }set { SetValue(ContentProperty, value); } }/// <summary> /// 標(biāo)識 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) { }以上代碼為一個(gè)相對完成的依賴屬性例子(還有一些功能比較少用就不寫出了),從這段代碼可以看出,自定義依賴屬性的步驟如下:
注冊依賴屬性并生成依賴屬性標(biāo)識符。依賴屬性標(biāo)識符為一個(gè)public static readonly DependencyProperty字段,在上面這個(gè)例子中,依賴屬性標(biāo)識符為ContentProperty。依賴屬性標(biāo)識符的名稱必須為“屬性名+Property”。在PropertyMetadata中指定屬性默認(rèn)值。
實(shí)現(xiàn)屬性包裝器。為屬性提供 CLR get 和 set 訪問器,在Getter和Setter中分別調(diào)用GetValue和SetValue。Getter和Setter中不應(yīng)該有其它任何自定義代碼。
注意: Setter中不要寫其它任何自定義代碼這點(diǎn)很重要,如果使用Binding或其它XAML中賦值的方式,程序并不會(huì)使用Setter,而是直接調(diào)用SetValue函數(shù)賦值。這也是為什么需要使用一個(gè)PropertyChangedCallback統(tǒng)一處理所有值變更事件,而不是直接寫在Setter里面。
如果需要監(jiān)視屬性值變更。可以在PropertyMetadata中定義一個(gè)PropertyChangedCallback方法。因?yàn)檫@個(gè)方法是靜態(tài)的,可以再實(shí)現(xiàn)一個(gè)同名的實(shí)例方法(可以參考ContentControl的OnContentChanged方法)。
5.2 代碼段
注冊依賴屬性的語法比較難記,可以使用VisualStudio自帶的代碼段propdp(輸入propdp后按兩次tab)自動(dòng)生成,這個(gè)代碼段生成的代碼只有基本功能,如下所示:
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>默認(rèn)值</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> /// 獲取或設(shè)置$MyProperty$的值 /// </summary> public $int$ $MyProperty$ { get { return ($int$)GetValue($MyProperty$Property); } set { SetValue($MyProperty$Property, value); } } /// <summary> /// 標(biāo)識 $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有了大幅簡化,需要學(xué)習(xí)的地方少了很多,但是功簡化了也不一定是一件好事。譬如下面這個(gè)代碼:
<StackPanel><Slider x:Name="SliderSource"Maximum="200" /> <Slider x:Name="SliderTarget" Maximum="100" Value="{Binding ElementName=SliderSource,Path=Value,Mode=OneWay}" /> </StackPanel>理想的情況下,拖動(dòng)SliderSource到100以后,因?yàn)镾liderTarget的Value已經(jīng)超過Maximum設(shè)置的100,所以沒有反應(yīng);再次把SliderSource拖動(dòng)到100以下,SliderTarget才會(huì)重新跟隨SliderSource改變。但實(shí)際上,之后無論再怎么拖動(dòng)SliderSource,SliderTarget都不會(huì)有反應(yīng)。
估計(jì)所有繼承自RangeBase的控件都會(huì)有這個(gè)BUG,如果要寫一個(gè)RangeBase控件(包含Value,Minimum,Maximum三個(gè)double值的控件,Value必須在后兩個(gè)值的范圍之間),這是個(gè)很讓人煩惱的問題。既然現(xiàn)在知道Value會(huì)被Maximum及Minimum約束,那么就可以猜想到問題出在ValueProperty的PropertyChangedCallback函數(shù)中。產(chǎn)生這個(gè)BUG的原因可以參考下面的代碼。
實(shí)際的代碼要復(fù)雜些,但基本的邏輯就是這樣。如果新的Value值超過了Maximum或Minimum,就將Value重新設(shè)置為Maximum或Minimum,保證Value不會(huì)超過設(shè)定的范圍。在這個(gè)例子里,如果在這個(gè)函數(shù)開頭的位置調(diào)用 range.ReadLocalValue(range.ValueProperty),返回的是一個(gè)Binding,在結(jié)尾的位置調(diào)用,返回的則是double類型的100,因?yàn)檫@段代碼將Value由OneWay Binding覆蓋為maximum的double值了。
在WPF中,這個(gè)問題并不存在,因?yàn)閃PF的依賴屬性可以使用CoerceValueCallback約束屬性值,而UWP的依賴屬性被簡化了,缺少這個(gè)功能。可以在網(wǎng)上用“Silverlight CoerceValue Helper”或“Silverlight CoerceValue Utils”等關(guān)鍵字試試搜索一些解決方案。為什么使用Silverlight的關(guān)鍵字來搜索?因?yàn)镾ilverlight同樣存在這個(gè)問題。雖然網(wǎng)上能找到不少解決方案,但以我的經(jīng)驗(yàn)來說沒有方案能很好地解決這個(gè)問題。最后我的解決方案如下:
/// <summary> /// 不要使用OneWayBinding,只能使用TwoWayBinding /// </summary> public object MyProperty {get { return (object)GetValue(MyPropertyProperty); }set { SetValue(MyPropertyProperty, value); } }反正不是寫控件庫給別人用,寫個(gè)注釋并且和同事打聲招呼就算了。
有興趣的話可以參考Silverlight RangeBase的源代碼,由于Silverlight和UWP比較接近,參考Silverlight的源碼基本就可以理解RangeBase的實(shí)現(xiàn)細(xì)節(jié)。
RangeBase.cs
這個(gè)是Silverlight的開源實(shí)現(xiàn)Moonlight的源碼,Moonlight的源碼對理解UWP、Silverlight都很有參考價(jià)值。順便一提,Silverlight的依賴屬性參考文檔也比UWP的依賴屬性參考文檔好用一些。
提示: 為什么使用TwoWay Binding可以解決這個(gè)問題?OneWay Binding和TwoWay Binding的內(nèi)部行為不同。使用OneWay Binding的情況下,給SliderTarget.Value設(shè)置一個(gè)值,意思就只是SliderTarget的Value需要設(shè)置成一個(gè)新的值,舍棄了之前的Binding。在TwoWay Binding的情況下,設(shè)置一個(gè)值的意思不止是Value會(huì)成為那個(gè)新的值,同時(shí)綁定的對象也會(huì)更新成這個(gè)值,TwoWay Binding 理所當(dāng)然地不能被舍棄。
7.參考
依賴屬性概述
自定義依賴屬性
Silverlight 依賴項(xiàng)屬性概述
總結(jié)
以上是生活随笔為你收集整理的[UWP]依赖属性2:使用依赖属性的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 20165306 我期望的师生关系
- 下一篇: 史上最简洁易懂的PGP邮件加密教程(MA