[UWP 自定義控件]了解模板化控件(5.2):UserControl vs. TemplatedControl


1. UserControl vs. TemplatedControl

在UWP中自定義控件常常會遇到這個問題:使用UserControl還是TemplatedControl來自定義控件。

1.1 使用UserControl自定義控件

  • 繼承自UserControl。
  • 由復數控件組合而成。
  • 包含XAML及CodeBehind。
  • 優點:
    • 上手簡單。
    • 可以在CodeBehind直接訪問UI元素。
    • 開發速度很快。
  • 缺點:
    • 不能使用ControlTemplate進行定制。
    • 通常很難繼承及擴展。
  • 使用場景:
    • 需要快速實現一個只有簡單功能的控件,而且無需擴展性。
    • 不需要可以改變UI。
    • 不需要在不同項目中共享控件。
  • 使用UserControl的控件:
    • Page及DropShadowPanel都是UserControl。

1.2 使用CustomControl自定義控件

  • 繼承自Control或其派生類。
  • 代碼和XAML分離,可以沒有XAML。
  • 可以使用ControlTemplate。
  • 控件庫中的控件通常都是CustomControl。
  • 優點:
    • 更加靈活,容易擴展。
    • UI和代碼分離。
  • 缺點:
    • 較高的上手難度。
  • 使用場景:
    • 需要一個可以擴展功能的靈活的控件。
    • 需要定制UI。
    • 需要在不同的項目中使用。
  • 使用CustomControl的控件:
    • 控件庫中提供的元素,除了直接繼承自FrameworkElement的Panel、Shape、TextBlock等少數元素,其它大部分都是CustomControl。

2. 實踐:使用UserControl實現DateTimeSelector

上一篇的DateTimeSelector例子很適合討這個問題。這個控件沒有復雜的邏輯,用UserControl的方式實現很簡單,代碼如下:

public sealed partial class DateTimeSelector3 : UserControl
{
    /// <summary>
    /// 標識 DateTime 依賴屬性。
    /// </summary>
    public static readonly DependencyProperty DateTimeProperty =
        DependencyProperty.Register("DateTime", typeof(DateTime?), typeof(DateTimeSelector3), new PropertyMetadata(null, OnDateTimeChanged));

    private static void OnDateTimeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        DateTimeSelector3 target = obj as DateTimeSelector3;
        DateTime? oldValue = (DateTime?)args.OldValue;
        DateTime? newValue = (DateTime?)args.NewValue;
        if (oldValue != newValue)
            target.OnDateTimeChanged(oldValue, newValue);
    }

    public DateTimeSelector3()
    {
        this.InitializeComponent();
        DateTime = System.DateTime.Now;
        TimeElement.TimeChanged += OnTimeChanged;
        DateElement.DateChanged += OnDateChanged;
    }


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

    private bool _isUpdatingDateTime;

    private void OnDateTimeChanged(DateTime? oldValue, DateTime? newValue)
    {
        _isUpdatingDateTime = true;
        try
        {
            if (DateElement != null && DateTime != null)
                DateElement.Date = DateTime.Value;

            if (TimeElement != null && DateTime != null)
                TimeElement.Time = DateTime.Value.TimeOfDay;
        }
        finally
        {
            _isUpdatingDateTime = false;
        }
    }

    private void OnDateChanged(object sender, DatePickerValueChangedEventArgs e)
    {
        UpdateDateTime();
    }

    private void OnTimeChanged(object sender, TimePickerValueChangedEventArgs e)
    {
        UpdateDateTime();
    }

    private void UpdateDateTime()
    {
        if (_isUpdatingDateTime)
            return;

        DateTime = DateElement.Date.Date.Add(TimeElement.Time);
    }
}

XAML:

<StackPanel>
    <DatePicker x:Name="DateElement" />
    <TimePicker x:Name="TimeElement"
                Margin="0,5,0,0" />
</StackPanel>

代碼真的很簡單,不需要GetTemplateChild,不需要DefaultStyleKey,不需要Blend,熟練的話大概5分鍾就能寫好一個。

使用UserControl有這些好處:

  • 快速。
  • 可以直接查看設計視圖,不需要用Blend。
  • 可以直接訪問XAML中的元素。

當然壞處也不少:

  • 不可以通過ControlTemplate修改UI。
  • 難以繼承並修改。
  • UI和代碼高度耦合。

如果控件只是內部使用,不是放在類庫中向第三者公開,也沒有修改的必要,使用UserControl也是合適的,畢竟它符合80/20原則:使用20%的時間完成了80%的功能。

3. 混合方案

如果需要快速實現控件,又需要適當的擴展能力,可以實現一個繼承UserControl的基類,再通過UserControl的方式派生這個基類。

public class DateTimeSelectorBase : UserControl

創建一個名為DateTimeSelectorBase的類,繼承自UserControl,其它代碼基本上照抄上一篇文章中的DatetimeSelector2,只不過刪除了構造函數中的代碼,因為不需要DefaultStyle。

然后用普通的方式新建一個UserControl,在XAML和CodeBehind中將基類改成DateTimeSelectorBase,如下所示:

<local:DateTimeSelectorBase x:Class="TemplatedControlSample.DateTimeSelector4"
                            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                            xmlns:local="using:TemplatedControlSample"
                            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                            mc:Ignorable="d"
                            x:Name="DateTimeSelector"
                            d:DesignHeight="300"
                            d:DesignWidth="400">
    <local:DateTimeSelectorBase.Resources>
        <local:DateTimeOffsetConverter x:Key="DateTimeOffsetConverter" />
        <local:TimeSpanConverter x:Key="TimeSpanConverter" />

    </local:DateTimeSelectorBase.Resources>
    <StackPanel>
        <DatePicker Margin="0,0,0,5"
                    Date="{Binding Date,ElementName=DateTimeSelector,Mode=TwoWay,Converter={StaticResource DateTimeOffsetConverter}}" />
        <TimePicker Time="{Binding Time,ElementName=DateTimeSelector,Mode=TwoWay}" />

    </StackPanel>
</local:DateTimeSelectorBase>
public sealed partial class DateTimeSelector4 : DateTimeSelectorBase
{
    public DateTimeSelector4()
    {
        this.InitializeComponent();
    }
}

這樣既可以在不同的派生類使用不同的UI,也可以使用設計視圖,結合了UserControl和TemplatedControl的優點。缺點是不可以使用ControlTemplate,而且不清楚這個控件的開發者會直觀地以為這是TemplatedControl,使用上會造成一些混亂。


免責聲明!

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



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