[UWP 自定義控件]了解模板化控件(2):模仿ContentControl


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

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

1. 定義屬性

/// <summary>
/// 獲取或設置Content的值
/// </summary>  
public object Content
{
    get { return (object)GetValue(ContentProperty); }
    set { SetValue(ContentProperty, value); }
}

/// <summary>
/// 標識 Content 依賴屬性。
/// </summary>
public static readonly DependencyProperty ContentProperty =
    DependencyProperty.Register("Content", typeof(object), typeof(MyContentControl), new PropertyMetadata(null, OnContentChanged));

private static void OnContentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    MyContentControl target = obj as MyContentControl;
    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)
{
}



/// <summary>
/// 獲取或設置ContentTemplate的值
/// </summary>  
public DataTemplate ContentTemplate
{
    get { return (DataTemplate)GetValue(ContentTemplateProperty); }
    set { SetValue(ContentTemplateProperty, value); }
}

/// <summary>
/// 標識 ContentTemplate 依賴屬性。
/// </summary>
public static readonly DependencyProperty ContentTemplateProperty =
    DependencyProperty.Register("ContentTemplate", typeof(DataTemplate), typeof(MyContentControl), new PropertyMetadata(null, OnContentTemplateChanged));

private static void OnContentTemplateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    MyContentControl target = obj as MyContentControl;
    DataTemplate oldValue = (DataTemplate)args.OldValue;
    DataTemplate newValue = (DataTemplate)args.NewValue;
    if (oldValue != newValue)
        target.OnContentTemplateChanged(oldValue, newValue);
}

protected virtual void OnContentTemplateChanged(DataTemplate oldValue, DataTemplate newValue)
{

}

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

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

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

2. 實現外觀

2.1 DefaultStyle

<Style TargetType="local:MyContentControl">
    <Setter Property="HorizontalContentAlignment"
            Value="Left" />
    <Setter Property="VerticalContentAlignment"
            Value="Top" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:MyContentControl">
                <ContentPresenter Content="{TemplateBinding Content}"
                                  ContentTemplate="{TemplateBinding ContentTemplate}"
                                  Margin="{TemplateBinding Padding}"
                                  HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                  VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

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

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

2.2 ContentPresenter

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

2.3 TemplateBinding

用於單向綁定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中的元素屬性綁定到所屬控件的屬性,例如Margin="{TemplateBinding Padding}",這樣可以方便控件的使用者通過屬性調整UI。

2.4 通過Setter改變默認值

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

2.5 ContentPropertyAttribute

<local:MyContentControl>
    <local:MyContentControl.Content>
        <Rectangle Height="100"
                   Width="100"
                   Fill="Red" />
    </local:MyContentControl.Content>
</local:MyContentControl>

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

[ContentProperty(Name = "Content")]
public class MyContentControl : Control

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

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

<local:MyContentControl>
    <Rectangle Height="100"
               Width="100"
               Fill="Red" />
</local:MyContentControl>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM