天天看點

[UWP]了解模闆化控件(2):模仿ContentControl

ContentControl是最簡單的TemplatedControl,而且它在UWP出場頻率很高。ContentControl和Panel是VisualTree的基礎,可以說幾乎所有VisualTree上的UI元素的父節點中總有一個ContentControl或Panel。

因為ContentControl很簡單,如果隻實作ContentControl最基本功能的話很适合用來做TemplatedControl的入門。這次的内容就是模仿ContentControl實作一個模闆化控件MyContentControl,直接繼承自Control。

MyContentControl隻實作ContentControl兩個最常用的屬性:Content和ContentTemplate。兩個都需要使用依賴屬性,這樣才可以使用Binding和下面會用到的TemplateBinding。

通常重要的屬性都會定義一個通知屬性值變更的virtual方法給派生類使用,如這裡的<code>protected virtual void OnContentChanged(object oldValue, object newValue)</code>。為了可以定義virtual方法,要移除類的sealed關鍵字。

值得一提的是Content屬性的類型是Object,這樣Content中既可以放文字,也可以放圖檔、Panel等元素。在UWP中如無特殊需求,Content、Header、Title等内容屬性最好都是Object類型,這樣更友善擴充,例如可以在Header放一個Checkbox,這是很常見的做法。

将Themes/Generic.xaml中TargetType="local:MyContentControl"的Style改寫成上述XAML。

UWP通過ControlTemplate定義控件的外觀。在MyContentControl中,ControlTemplate隻有一個元素ContentPresenter,它使用TemplateBinding綁定到自己所在的MyContentControl的公共屬性。對經常使用ControlTemplate的開發者來說ContentPresenter和TemplateBinding都不是陌生的概念。

ContentPresenter用于顯示内容,預設綁定到ContentControl的Content屬性。基本上所有ContentControl中都包含一個ContentPresenter。ContentPresenter直接從FrameworkElement派生。

用于單向綁定ControlTemplate所在控件的功能屬性,例如Margin="{TemplateBinding Padding}"幾乎等效于Margin="{Binding Margin,RelativeSource={RelativeSource Mode=TemplatedParent},Mode=OneWay}",相當于一種簡化的寫法。但它們之間有如下不同:

TemplateBinding隻能用在ControlTemplate中。

TemplateBinding的源和目标屬性都必須是依賴屬性。

TemplateBinding不能使用TypeConverter,是以源屬性和目标屬性必須為相同的資料類型。

通常在ContentPresenter上使用TemplateBinding的屬性不會太多,因為很大一部分Control的屬性都是可屬性值繼承的,即預設使用VisualTree上父節點所設定的屬性值,譬如字型屬性(如FontSize、FontFamily)、DataContext等。

除了可屬性值繼承的屬性,需要适當地将ControlTemplate中的元素屬性綁定到所屬控件的屬性,例如<code>Margin="{TemplateBinding Padding}"</code>,這樣可以友善控件的使用者通過屬性調整UI。

通常從父類繼承而來的屬性不會在構造函數中設定預設值,而是在DefaultStyle的Setter中設定預設值。MyContentControl為了将HorizontalContentAlignment改為Left而在Style添加了Property="HorizontalContentAlignment"的Setter。

使用MyContentControl的XAML如上所示,但看起來和ContentControl不同,多了 <code>local:MyContentControl.Content</code> 這行。解決辦法是添加Windows.UI.Xaml.Markup.ContentPropertyAttribute到MyContentControl上。

在MyContentControl使用這個Attribute,UWP在解釋XAML時,會将XAML的内容識别為MyContentControl的Content屬性。除了可以省略兩行XAML外,ContentPropertyAttribute還有指出類的主要屬性的作用。譬如Panel添加了<code>[ContentProperty(Name = "Children")]</code>,TextBlock添加了<code>[ContentProperty(Name = "Inlines")]</code>。

添加ContentPropertyAttribute後,使用MyContentControl的XAML和ContentControl就基本一緻了。

繼續閱讀