[UWP 自定義控件]了解模板化控件(3):實現HeaderedContentControl


1. 概述

來看看這段XMAL:

<StackPanel Width="300">
    <TextBox Header="TextBox" />
    <ComboBox Header="ComboBox" HorizontalAlignment="Stretch"/>
    <AutoSuggestBox Header="AutoSuggestBox" />
    <TextBlock Text="ListBox" />
    <ListBox>
        <ListBoxItem Content="ListBoxItem 1" />
        <ListBoxItem Content="ListBoxItem 2" />
        <ListBoxItem Content="ListBoxItem 3" />
    </ListBox>
</StackPanel>

是不是覺得它們中出了一個叛徒?這個示例中除了ListBox控件其它都自帶Header,但是ListBox沒有Header屬性,只好用一個TextBlock模仿它的Header。這樣就帶來一個問題:只有ListBox的Header高度和其它控件不一致。

既然現在討論的是自定義控件,這里就用自定義控件的方式解決這個問題。首先想到最簡單的方法,就是自定義一個HeaderedContentControl,如名字所示,這個控件繼承自ContentControl並擁有Header屬性,用起來大概是這樣:

<HeaderedContentControl Header="ListBox">
    <ListBox/>
</HeaderedContentControl>

這樣,只要在HeaderedContentControl的樣式中模仿其它含Header屬性的控件,就能統一Header的外觀。

WPF中本來就有這個控件,它是Expander、GroupBox、TabItem等諸多擁有Header屬性的控件的基類,十分方便好用。UWP中模仿這個控件很簡單,而且很適合用來學習自定義控件的進階知識。

2. 定義HeaderedContentControl結構

比起WPF,借鑒Silverlight的HeaderedContentControl比較好,因為Silverlight的比較簡單。HeaderedContentControl只需要在繼承ContentControl后添加兩個屬性:Header和HeaderTemplate。

public class HeaderedContentControl : ContentControl
{
    public HeaderedContentControl()
    {
        this.DefaultStyleKey = typeof(HeaderedContentControl);
    }

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

    /// <summary>
    /// 標識 Header 依賴屬性。
    /// </summary>
    public static readonly DependencyProperty HeaderProperty =
        DependencyProperty.Register("Header", typeof(object), typeof(HeaderedContentControl), new PropertyMetadata(null, OnHeaderChanged));

    private static void OnHeaderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        HeaderedContentControl target = obj as HeaderedContentControl;
        object oldValue = (object)args.OldValue;
        object newValue = (object)args.NewValue;
        if (oldValue != newValue)
            target.OnHeaderChanged(oldValue, newValue);
    }

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

    /// <summary>
    /// 標識 HeaderTemplate 依賴屬性。
    /// </summary>
    public static readonly DependencyProperty HeaderTemplateProperty =
        DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(HeaderedContentControl), new PropertyMetadata(null, OnHeaderTemplateChanged));

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

    protected virtual void OnHeaderChanged(object oldValue, object newValue)
    {
    }

    protected virtual void OnHeaderTemplateChanged(DataTemplate oldValue, DataTemplate newValue)
    {
    }
}

3. 設計樣式

在C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.14393.0\Generic\generic.xaml中找到ContentControl的樣式。

再從TextBox的Style中找到HeaderContentPresenter

提示: 隨便找個有ThemeResource的XAML,譬如Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}",在資源名稱(ApplicationPageBackgroundThemeBrush)上按"F12",即可導航到存放ThemeResource的generic.xaml。

組合起來,HeaderedContentControl的默認樣式就完成了。

<Style TargetType="local:HeaderedContentControl">
    <Setter Property="HorizontalContentAlignment"
            Value="Left" />
    <Setter Property="VerticalContentAlignment"
            Value="Top" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:HeaderedContentControl">
                <StackPanel>
                    <ContentPresenter x:Name="HeaderContentPresenter"
                                      Foreground="{ThemeResource TextControlHeaderForeground}"
                                      Margin="0,0,0,8"
                                      Content="{TemplateBinding Header}"
                                      ContentTemplate="{TemplateBinding HeaderTemplate}"
                                      FontWeight="Normal" />
                    <ContentPresenter Content="{TemplateBinding Content}"
                                      ContentTemplate="{TemplateBinding ContentTemplate}"
                                      Margin="{TemplateBinding Padding}"
                                      ContentTransitions="{TemplateBinding ContentTransitions}"
                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                      VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
                </StackPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

4. 使用

 <StackPanel Visibility="Collapsed">
    <TextBox Header="TextBox" />
    <ComboBox Header="ComboBox"
              HorizontalAlignment="Stretch" />
    <AutoSuggestBox Header="AutoSuggestBox" />
    <local:HeaderedContentControl Header="ListBox">
        <ListBox>
            <ListBoxItem Content="ListBoxItem 1" />
            <ListBoxItem Content="ListBoxItem 2" />
            <ListBoxItem Content="ListBoxItem 3" />
        </ListBox>
    </local:HeaderedContentControl>
</StackPanel>

調用代碼及效果。這樣外觀就統一了。

注意: 我移除了 x:DeferLoadStrategy="Lazy"這句,這個知識點比較適合放在有關性能的主題里討論。


免責聲明!

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



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