以上代碼為一個相對完成的依賴屬性例子(還有一些功能比較少用就不寫出了),從這段代碼可以看出,自定義依賴屬性的步驟如下:
注冊依賴屬性并生成依賴屬性辨別符。依賴屬性辨別符為一個public static readonly DependencyProperty字段,在上面這個例子中,依賴屬性辨別符為ContentProperty。依賴屬性辨別符的名稱必須為“屬性名+Property”。在PropertyMetadata中指定屬性預設值。
實作屬性包裝器。為屬性提供 CLR get 和 set 通路器,在Getter和Setter中分别調用GetValue和SetValue。Getter和Setter中不應該有其它任何自定義代碼。
注意: Setter中不要寫其它任何自定義代碼這點很重要,如果使用Binding或其它XAML中指派的方式,程式并不會使用Setter,而是直接調用SetValue函數指派。這也是為什麼需要使用一個PropertyChangedCallback統一處理所有值變更事件,而不是直接寫在Setter裡面。
如果需要監視屬性值變更。可以在PropertyMetadata中定義一個PropertyChangedCallback方法。因為這個方法是靜态的,可以再實作一個同名的執行個體方法(可以參考ContentControl的OnContentChanged方法)。
注冊依賴屬性的文法比較難記,可以使用VisualStudio自帶的代碼段propdp(輸入propdp後按兩次tab)自動生成,這個代碼段生成的代碼隻有基本功能,如下所示:
要生成完整的依賴屬性代碼,可以使用自定義的代碼段,以下代碼段生成的就是完整的依賴屬性定義,快捷鍵是dp:
UWP的依賴屬性比起WPF有了大幅簡化,需要學習的地方少了很多,但是功簡化了也不一定是一件好事。譬如下面這個代碼:
理想的情況下,拖動SliderSource到100以後,因為SliderTarget的Value已經超過Maximum設定的100,是以沒有反應;再次把SliderSource拖動到100以下,SliderTarget才會重新跟随SliderSource改變。但實際上,之後無論再怎麼拖動SliderSource,SliderTarget都不會有反應。
估計所有繼承自RangeBase的控件都會有這個BUG,如果要寫一個RangeBase控件(包含Value,Minimum,Maximum三個double值的控件,Value必須在後兩個值的範圍之間),這是個很讓人煩惱的問題。既然現在知道Value會被Maximum及Minimum限制,那麼就可以猜想到問題出在ValueProperty的PropertyChangedCallback函數中。産生這個BUG的原因可以參考下面的代碼。
實際的代碼要複雜些,但基本的邏輯就是這樣。如果新的Value值超過了Maximum或Minimum,就将Value重新設定為Maximum或Minimum,保證Value不會超過設定的範圍。在這個例子裡,如果在這個函數開頭的位置調用 range.ReadLocalValue(range.ValueProperty),傳回的是一個Binding,在結尾的位置調用,傳回的則是double類型的100,因為這段代碼将Value由OneWay Binding覆寫為maximum的double值了。
在WPF中,這個問題并不存在,因為WPF的依賴屬性可以使用CoerceValueCallback限制屬性值,而UWP的依賴屬性被簡化了,缺少這個功能。可以在網上用“Silverlight CoerceValue Helper”或“Silverlight CoerceValue Utils”等關鍵字試試搜尋一些解決方案。為什麼使用Silverlight的關鍵字來搜尋?因為Silverlight同樣存在這個問題。雖然網上能找到不少解決方案,但以我的經驗來說沒有方案能很好地解決這個問題。最後我的解決方案如下:
反正不是寫控件庫給别人用,寫個注釋并且和同僚打聲招呼就算了。
有興趣的話可以參考Silverlight RangeBase的源代碼,由于Silverlight和UWP比較接近,參考Silverlight的源碼基本就可以了解RangeBase的實作細節。
<a href="https://github.com/mono/moon/blob/cb343939d3f5731d8c1509beb90c051c63a83903/class/Microsoft.SilverlightControls/Controls/Src/RangeBase/RangeBase.cs">RangeBase.cs</a>
這個是Silverlight的開源實作Moonlight的源碼,Moonlight的源碼對了解UWP、Silverlight都很有參考價值。順便一提,Silverlight的依賴屬性參考文檔也比UWP的依賴屬性參考文檔好用一些。
提示: 為什麼使用TwoWay Binding可以解決這個問題?OneWay Binding和TwoWay Binding的内部行為不同。使用OneWay Binding的情況下,給SliderTarget.Value設定一個值,意思就隻是SliderTarget的Value需要設定成一個新的值,舍棄了之前的Binding。在TwoWay Binding的情況下,設定一個值的意思不止是Value會成為那個新的值,同時綁定的對象也會更新成這個值,TwoWay Binding 理所當然地不能被舍棄。
<a href="https://msdn.microsoft.com/zh-cn/windows/uwp/xaml-platform/dependency-properties-overview">依賴屬性概述</a>
<a href="https://msdn.microsoft.com/zh-cn/windows/uwp/xaml-platform/custom-dependency-properties">自定義依賴屬性</a>
<a href="https://msdn.microsoft.com/zh-cn/library/cc221408(v=vs.95).aspx#%E4%B8%AD%E5%9B%BD%20(%E7%AE%80%E4%BD%93%E4%B8%AD%E6%96%87)">Silverlight 依賴項屬性概述</a>