[UWP]了解模板化控件(4):TemplatePart
1. TemplatePart
TemplatePart(部件)是指ControlTemplate中的命名元素。控件邏輯預(yù)期這些部分存在于ControlTemplate中,并且使用protected DependencyObject GetTemplateChild(String childName)獲取它們后進(jìn)行操作。
以AutoSuggestBox為例,它的ControlTemplate結(jié)構(gòu)如下,可以看到AutoSuggestBox由四個(gè)TemplatePart組成,每個(gè)TemplatePart都可以在控件代碼中以編程方式訪問:
下圖顯示了AutoSuggestBox的TemplatePart:
2. 使用TemplatePart
上一篇文章構(gòu)造了一個(gè)很基礎(chǔ)的控件HeaderedContentControl,這次通過擴(kuò)展這個(gè)類做些試驗(yàn)性質(zhì)的功能來介紹模板化控件的進(jìn)階知識(shí)。
新建一個(gè)名為ContentView的控件,繼承自HeaderedContentControl,它要實(shí)現(xiàn)的功能有兩個(gè):
- 控件的Header默認(rèn)Opacity=0.7,當(dāng)鼠標(biāo)移動(dòng)到控件上時(shí),設(shè)置Header的Opacity=1。
- 當(dāng)Header為空時(shí),隱藏用于顯示Header的HeaderContentPresenter。
雖然可以使用依賴屬性及TemplateBinding的方式實(shí)現(xiàn)這個(gè)需求,不過這次用TemplatePart的方式實(shí)現(xiàn)。很顯然,要實(shí)現(xiàn)這次的需求最直接的做法是獲取顯示Header的TemplatePart,然后用代碼對(duì)其進(jìn)行操作。大致上分為兩步:添加TemplatePart名稱,在代碼中獲取這個(gè)部件并操作。
2.1 添加TemplatePart名稱
在ContentView的ControlTemplate中為ContentPresenter命名為HeaderContentPresenter:
<ContentPresenter x:Name="HeaderContentPresenter" Foreground="{ThemeResource TextControlHeaderForeground}" Margin="0,0,0,8" FontWeight="Normal" Content="{TemplateBinding Header}" ContentTemplate="{TemplateBinding HeaderTemplate}" />2.2 獲取TemplatePart
模板化控件在加載ControlTemplate后會(huì)調(diào)用OnApplyTemplate,可以在這個(gè)函數(shù)中調(diào)用protected DependencyObject GetTemplateChild(String childName)獲取模板中指定名字的部件。從返回值是DependencyObject可以看出,只要是DependencyObject 都能使用ControlTemplate獲取。
這段代碼演示了如何獲得顯示Header的ContentPresenter部件:
protected override void OnApplyTemplate() {base.OnApplyTemplate();_headerPart = GetTemplateChild(HeaderPartName) as FrameworkElement; }注意:不要在Loaded事件中嘗試調(diào)用GetTemplateChild,因?yàn)長(zhǎng)oaded在OnApplyTemplate前調(diào)用,而且Loaded更容易被多次觸發(fā)。
由于Template可能多次加載,或者不能正確獲取TemplatePart,所以使用TemplatePart前應(yīng)該先判斷是否為空;如果要訂閱事件,應(yīng)該先取消訂閱。更完整的GetTemplateChild步驟應(yīng)該是:
- 取消訂閱TemplatePart事件
- 將TemplatePart存儲(chǔ)到私有字段
- 訂閱TemplatePart事件
可以參考如下代碼:
public override void OnApplyTemplate() {base.OnApplyTemplate();if (_button != null){_button.Click -= OnButtonClick;}_button = GetTemplateChild(PartButtonName) as ButtonBase;if (_button != null){_button.Click += OnButtonClick;} }2.3 完整的代碼
[TemplatePart(Name = HeaderPartName, Type = typeof(FrameworkElement))] public sealed class ContentView : HeaderedContentControl {public const string HeaderPartName = "HeaderContentPresenter";public ContentView(){this.DefaultStyleKey = typeof(ContentView);}private FrameworkElement _headerPart;private bool _isPointerEntered;protected override void OnApplyTemplate(){base.OnApplyTemplate();_headerPart = GetTemplateChild(HeaderPartName) as FrameworkElement;UpdateHeaderVisual();}protected override void OnPointerEntered(PointerRoutedEventArgs e){base.OnPointerEntered(e);_isPointerEntered = true;UpdateHeaderVisual();}protected override void OnPointerExited(PointerRoutedEventArgs e){base.OnPointerExited(e);_isPointerEntered = false;UpdateHeaderVisual();}protected override void OnHeaderChanged(object oldValue, object newValue){base.OnHeaderChanged(oldValue, newValue);UpdateHeaderVisual();}private void UpdateHeaderVisual(){if (_headerPart == null)return;if (_isPointerEntered)_headerPart.Opacity = 1;else_headerPart.Opacity = 0.7;if (Header == null)_headerPart.Visibility = Visibility.Collapsed;else_headerPart.Visibility = Visibility.Visible;} }3. x:DeferLoadStrategy="Lazy"與GetTemplateChild
標(biāo)記為x:DeferLoadStrategy="Lazy"的元素將延遲加載,即不會(huì)出現(xiàn)在VisualTree上,直到它被調(diào)用。
假設(shè)將ContentView中HeaderContentPresenter標(biāo)記為x:DeferLoadStrategy="Lazy"并且在代碼中注釋_headerPart = GetTemplateChild(HeaderPartName) as FrameworkElement這句,運(yùn)行時(shí)將看不到Header的內(nèi)容,并且VisualTree如下所示:
只有代碼中執(zhí)行了_headerPart = GetTemplateChild(HeaderPartName) as FrameworkElement這句后,VisualTree上才可以看到HeaderContentPresenter,如下所示:
出于性能方面的考慮,很多UWP原生控件都會(huì)包含x:DeferLoadStrategy="Lazy"。
4. TemplatePartAttribute協(xié)定
有時(shí),為了表明控件期待在ControlTemplate存在某個(gè)特定部件,防止編輯ControlTemplate的開發(fā)人員刪除它,控件上會(huì)添加添加TemplatePartAttribute協(xié)定。上面的ContentView代碼中即包含這個(gè)協(xié)定:
[TemplatePart(Name = HeaderPartName, Type = typeof(FrameworkElement))]這段代碼的意思是期待在ControlTemplate中存在名稱為 "HeaderContentPresenter",類型為FrameworkElement的部件。
TemplatePartAttribute在UWP中的作用好像被弱化了,不止在UWP原生控件中見不到TemplatePartAttribute,甚至在Blend中“部件”窗口也消失了。可能UWP更加建議使用VisualState。
注意:你可能會(huì)在別的地方看到部件的命名為“PART_”開頭,在WPF時(shí)代確實(shí)是這樣,到現(xiàn)在仍有很多人保留了這種習(xí)慣。新興的命名語(yǔ)法更加自然,不需要加上“PART_”開頭。不過既然Blend中沒有了“部件”窗口,用“PART_”標(biāo)識(shí)部件也是個(gè)不錯(cuò)的方法。
5. 原則
使用TemplatePart需要遵循以下原則:
- 盡可能減少TemplarePartAttribute協(xié)定。
- 在使用TemplatePart之前檢查其是否為Null。
- 如果ControlTemplate沒有遵循TemplatePartAttribute協(xié)定也不應(yīng)該拋出異常,有可能ControlTemplate的作者是故意屏蔽某項(xiàng)功能。
總結(jié)
以上是生活随笔為你收集整理的[UWP]了解模板化控件(4):TemplatePart的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 贝叶斯分类器_python机器学习API
- 下一篇: miniconda的安装与配置_Mini