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

在前两篇文章中,我们分别使用了templatepart和visualstate的方法实现了相同的功能,其中visualstate显然更具灵活性。在这种情况下,我通常更倾向于使用visualstate。然而,在实际应用中,这两种实现方式并不是互斥的,许多模板化控件会同时使用这两种方法。

使用VisualState的好处包括:

代码和UI分离,使得控件扩展更灵活。可以使用Blend轻松实现动画。

尽管VisualState有诸多优势,但并不是说所有功能都必须使用VisualState实现。以下情况我会选择使用TemplatePart:

需要快速实现一个控件。某个行为是固定的,不需要扩展。需要在代码中操作UI,例如Slider或ComboBox。为了强调某个部件是控件必需的。为了隐藏实现细节,限制派生类或ControlTemplate修改重要的逻辑。

其中,使用TemplatePart产生的扩展性问题是我谨慎使用这种方案的最大因素。

除了VisualState,TemplatePart的功能也常常会被TemplateBinding所替代。前面的例子展示了使用VisualState在UI上的优势,这次我们用另一个控件DateTimeSelector来讨论使用TemplatePart在扩展性上的其他问题。

使用TemplatePart

DateTimeSelector组合了CalendarDatePicker和TimePicker,用于选择日期和时间(SelectedDateTime)。其XAML如下:

                                                                                

相应的代码如下:

[TemplatePart(Name = DateElementPartName, Type = typeof(CalendarDatePicker))][TemplatePart(Name = TimeElementPartName, Type = typeof(TimePicker))]public class DateTimeSelector : Control{    public const string DateElementPartName = "DateElement";    public const string TimeElementPartName = "TimeElement";    public static readonly DependencyProperty SelectedDateTimeProperty =        DependencyProperty.Register("SelectedDateTime", typeof(DateTime), typeof(DateTimeSelector), new PropertyMetadata(DateTime.Now, OnSelectedDateTimeChanged));    private static void OnSelectedDateTimeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)    {        DateTimeSelector target = obj as DateTimeSelector;        DateTime oldValue = (DateTime)args.OldValue;        DateTime newValue = (DateTime)args.NewValue;        if (oldValue != newValue)            target.OnSelectedDateTimeChanged(oldValue, newValue);    }    public DateTimeSelector()    {        this.DefaultStyleKey = typeof(DateTimeSelector);    }    public DateTime SelectedDateTime    {        get { return (DateTime)GetValue(SelectedDateTimeProperty); }        set { SetValue(SelectedDateTimeProperty, value); }    }    private CalendarDatePicker _dateElement;    private TimePicker _timeElement;    private bool _isUpdatingDateTime;    protected override void OnApplyTemplate()    {        base.OnApplyTemplate();        if (_dateElement != null)            _dateElement.DateChanged -= OnDateElementDateChanged;        _dateElement = GetTemplateChild(DateElementPartName) as CalendarDatePicker;        if (_dateElement != null)            _dateElement.DateChanged += OnDateElementDateChanged;        if (_timeElement != null)            _timeElement.TimeChanged -= OnTimeElementTimeChanged;        _timeElement = GetTemplateChild(TimeElementPartName) as TimePicker;        if (_timeElement != null)            _timeElement.TimeChanged += OnTimeElementTimeChanged;        UpdateElement();    }    protected virtual void OnSelectedDateTimeChanged(DateTime oldValue, DateTime newValue)    {        UpdateElement();    }    private void OnDateElementDateChanged(CalendarDatePicker sender, CalendarDatePickerDateChangedEventArgs args)    {        UpdateSelectDateTime();    }    private void OnTimeElementTimeChanged(object sender, TimePickerValueChangedEventArgs e)    {        UpdateSelectDateTime();    }    private void UpdateElement()    {        _isUpdatingDateTime = true;        try        {            if (_dateElement != null)                _dateElement.Date = SelectedDateTime.Date;            if (_timeElement != null)                _timeElement.Time = SelectedDateTime.TimeOfDay;        }        finally        {            _isUpdatingDateTime = false;        }    }    private void UpdateSelectDateTime()    {        if (_isUpdatingDateTime)            return;        DateTime dateTime = DateTime.Now;        if (_dateElement != null && _dateElement.Date.HasValue)            dateTime = _dateElement.Date.Value.Date;        if (_timeElement != null)            dateTime = dateTime.Add(_timeElement.Time);        SelectedDateTime = dateTime;    }}

可以看出,DateTimeSelector通过监视CalendarDatePicker的DateChanged和TimePicker的TimeChanged来改变SelectedDateTime的值。

DateTimeSelector的代码非常简单,控件也运行良好,但如果某天需要将CalendarDatePicker替换为DatePicker或某个第三方的日期选择控件,DateTimeSelector就无能为力了,既不能通过修改ControlTemplate,也不能通过继承来达到目的。

使用TemplateBinding

通常在构建这类控件时,应先考虑其数据和行为,而不关心其UI。DateTimeSelector最核心的功能是通过选择Date和Time得出组合起来的DateTime,那么就可以先写出如下的类:

public class DateTimeSelector2 : Control{    public static readonly DependencyProperty DateProperty =        DependencyProperty.Register("Date", typeof(DateTime), typeof(DateTimeSelector2), new PropertyMetadata(DateTime.Now, OnDateChanged));    private static void OnDateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)    {        DateTimeSelector2 target = obj as DateTimeSelector2;        DateTime oldValue = (DateTime)args.OldValue;        DateTime newValue = (DateTime)args.NewValue;        if (oldValue != newValue)            target.OnDateChanged(oldValue, newValue);    }    public static readonly DependencyProperty TimeProperty =        DependencyProperty.Register("Time", typeof(TimeSpan), typeof(DateTimeSelector2), new PropertyMetadata(TimeSpan.Zero, OnTimeChanged));    private static void OnTimeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)    {        DateTimeSelector2 target = obj as DateTimeSelector2;        TimeSpan oldValue = (TimeSpan)args.OldValue;        TimeSpan newValue = (TimeSpan)args.NewValue;        if (oldValue != newValue)            target.OnTimeChanged(oldValue, newValue);    }    public static readonly DependencyProperty DateTimeProperty =        DependencyProperty.Register("DateTime", typeof(DateTime), typeof(DateTimeSelector2), new PropertyMetadata(DateTime.Now, OnDateTimeChanged));    private static void OnDateTimeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)    {        DateTimeSelector2 target = obj as DateTimeSelector2;        DateTime oldValue = (DateTime)args.OldValue;        DateTime newValue = (DateTime)args.NewValue;        if (oldValue != newValue)            target.OnDateTimeChanged(oldValue, newValue);    }    public DateTimeSelector2()    {        this.DefaultStyleKey = typeof(DateTimeSelector2);    }    public DateTime Date    {        get { return (DateTime)GetValue(DateProperty); }        set { SetValue(DateProperty, value); }    }    public TimeSpan Time    {        get { return (TimeSpan)GetValue(TimeProperty); }        set { SetValue(TimeProperty, value); }    }    public DateTime DateTime    {        get { return (DateTime)GetValue(DateTimeProperty); }        set { SetValue(DateTimeProperty, value); }    }    private bool _isUpdatingDateTime;    protected virtual void OnDateChanged(DateTime oldValue, DateTime newValue)    {        UpdateDateTime();    }    protected virtual void OnTimeChanged(TimeSpan oldValue, TimeSpan newValue)    {        UpdateDateTime();    }    protected virtual void OnDateTimeChanged(DateTime oldValue, DateTime newValue)    {        _isUpdatingDateTime = true;        try        {            Date = newValue.Date;            Time = newValue.TimeOfDay;        }        finally        {            _isUpdatingDateTime = false;        }    }    private void UpdateDateTime()    {        if (_isUpdatingDateTime)            return;        DateTime = Date.Date.Add(Time);    }}

控件的代码并不清楚ControlTemplate中包含什么控件,它只关心自己的数据。

在XAML中通过绑定使用这些数据。

                                                                                                                                                                

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

这里给出了两个Style,分别使用了CalendarDatePicker和DatePicker,通过TwoWay Binding访问DateTimeSelector2中的Date属性。如果你的TemplatedControl需要有良好的扩展能力,可以尝试使用这种方式。

以上就是[UWP 自定义控件]了解模板化控件(5.1):TemplatePart vs. VisualState的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/191949.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月1日 13:32:01
下一篇 2025年11月1日 13:32:33

相关推荐

发表回复

登录后才能评论
关注微信