[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,使用上会造成一些混乱。

时间: 2024-08-10 01:53:06

[UWP]了解模板化控件(5.2):UserControl vs. TemplatedControl的相关文章

[UWP]了解模板化控件(5.1):TemplatePart vs. VisualState

1. TemplatePart vs. VisualState 在前面两篇文章中分别使用了TemplatePart及VisualState的方式实现了相同的功能,其中明显VisualState的方式更灵活一些.如果遇到这种情况通常我更倾向使用VisualState.不过在实际应用中这两种实现方式并不是互斥的,很多模板化控件都同时使用这两种方式, 使用VisualState有如下好处: 代码和UI分离. 可以更灵活地扩展控件. 可以使用Blend轻松实现动画. 并不是说VisualState好处这

[UWP]了解模板化控件(4):TemplatePart

1. TemplatePart TemplatePart(部件)是指ControlTemplate中的命名元素.控件逻辑预期这些部分存在于ControlTemplate中,并且使用protected DependencyObject GetTemplateChild(String childName)获取它们后进行操作. 以AutoSuggestBox为例,它的ControlTemplate结构如下,可以看到AutoSuggestBox由四个TemplatePart组成,每个TemplatePa

[UWP]了解模板化控件(5):VisualState

1. 功能需求 使用TemplatePart实现上篇文章的两个需求(Header为空时隐藏HeaderContentPresenter,鼠标没有放在控件上时HeaderContentPresent半透明),虽然功能已经实现,但这样实现的话基本上也就别想扩展了.譬如开发者做不到通过继承或修改ControlTemplate实现如下功能: 半透明时的Opacity不是0.7,而是0.5. 半透明和不透明之前切换时有渐变动画. 当然也并不是不可以用代码实现这些需求,只是会复杂很多.大部分的开发者都是对C

uwp开发-UserControl传参给Page页面

usercontrol.xaml <Grid> <Button x:Name="Button" Content="点我,我就把参数传给Page了" Click="Button_Click"></Button> </Grid> usercontrol.xaml.cs namespace UserControlParameterPageDemo { //自定义一个用于传递参数的委托类型,把待传参数放置委

UWP UserControl 不会自适应大小

在一般的Page里面,我们通过VisualStateManager,可以根据窗体的宽度,来调整一些控件大小. <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="WindowStates"> <VisualState x:Name="PanoramicState"> <VisualState.StateTriggers> <Adapti

UWP中的消息提示框(一)

不管什么平台,应用内难免会出现一些消息提示框,下面就来聊聊我在UWP里用到的消息提示框. 弹窗也可按是否需要用户操作促发一些逻辑进行分为两大类. 不需要用户干涉的一类: MessageDialog:操作简单,写起来也省事,想具体了解的请参考MSDN 先看看效果 PC上效果: mobile上效果: 再看看代码(●'?'●) 前台: <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" >

win10 uwp MVVM入门

MVVM 是一个强大的架构,基本从 WPF 开始,wr(我说的就是微软)就提倡使用 MVVM.它可以将界面和后台分离,让开发人员可以不关心界面是怎样,全心投入到后台代码编写中. 然后在编写完后台代码后,可以快速和界面设计师做出来的界面绑定到一起,即使频繁修改界面也几乎不需要去修改后台代码. 更让人喜欢的是,他可以让我们简单地进行单元测试,因为我们可以不打开界面进行测试功能,方便了我们的测试开发. UWP 虽然可以直接在xaml.cs 写逻辑但是我们是推荐使用 MVVM 框架,写一个自己的框架也很

Windows10(UWP)下的MEF

前言 最近在帮一家知名外企开发Universal Windows Platform的相关应用,开发过程中不由感慨:项目分为两种,一种叫做前人栽树后人乘凉,一种叫做前人挖坑后人遭殃.不多说了,多说又要变成月经贴了. 讲讲MEF. MEF全称Managed Extensibility Framework.我们做.Net的碰到依赖注入(DI:Dependency Injection)这一块的内容,一般会选择使用Unity或者MEF,这也是Prism主要使用的两种方式.在.Net 4.0之前,MEF一直

在 UWP 中实现 Expander 控件

WPF 中的 Expander 控件在 Windows 10 SDK 中并不提供,本文主要说明,如何在 UWP 中创建这样一个控件.其效果如下图: 首先,分析该控件需要的一些特性,它应该至少包括如下三个属性: Content: 最重要的属性,设置该属性,可以使 Expander 控件显示其内容: Header: 控件的 Header: IsExpand: 当前是否展开. 接下来是定义其 UI,在这里使用 Grid,添加两行,一行显示 Header,一行显示 Content,当 IsExpand