0

0

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

爱谁谁

爱谁谁

发布时间:2025-09-10 08:42:01

|

982人浏览过

|

来源于php中文网

原创

在前两篇文章中,我们分别使用了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的值。

arXiv Xplorer
arXiv Xplorer

ArXiv 语义搜索引擎,帮您快速轻松的查找,保存和下载arXiv文章。

下载

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需要有良好的扩展能力,可以尝试使用这种方式。

相关专题

更多
php源码安装教程大全
php源码安装教程大全

本专题整合了php源码安装教程,阅读专题下面的文章了解更多详细内容。

65

2025.12.31

php网站源码教程大全
php网站源码教程大全

本专题整合了php网站源码相关教程,阅读专题下面的文章了解更多详细内容。

43

2025.12.31

视频文件格式
视频文件格式

本专题整合了视频文件格式相关内容,阅读专题下面的文章了解更多详细内容。

35

2025.12.31

不受国内限制的浏览器大全
不受国内限制的浏览器大全

想找真正自由、无限制的上网体验?本合集精选2025年最开放、隐私强、访问无阻的浏览器App,涵盖Tor、Brave、Via、X浏览器、Mullvad等高自由度工具。支持自定义搜索引擎、广告拦截、隐身模式及全球网站无障碍访问,部分更具备防追踪、去谷歌化、双内核切换等高级功能。无论日常浏览、隐私保护还是突破地域限制,总有一款适合你!

41

2025.12.31

出现404解决方法大全
出现404解决方法大全

本专题整合了404错误解决方法大全,阅读专题下面的文章了解更多详细内容。

204

2025.12.31

html5怎么播放视频
html5怎么播放视频

想让网页流畅播放视频?本合集详解HTML5视频播放核心方法!涵盖<video>标签基础用法、多格式兼容(MP4/WebM/OGV)、自定义播放控件、响应式适配及常见浏览器兼容问题解决方案。无需插件,纯前端实现高清视频嵌入,助你快速打造现代化网页视频体验。

9

2025.12.31

关闭win10系统自动更新教程大全
关闭win10系统自动更新教程大全

本专题整合了关闭win10系统自动更新教程大全,阅读专题下面的文章了解更多详细内容。

8

2025.12.31

阻止电脑自动安装软件教程
阻止电脑自动安装软件教程

本专题整合了阻止电脑自动安装软件教程,阅读专题下面的文章了解更多详细教程。

3

2025.12.31

html5怎么使用
html5怎么使用

想快速上手HTML5开发?本合集为你整理最实用的HTML5使用指南!涵盖HTML5基础语法、主流框架(如Bootstrap、Vue、React)集成方法,以及无需安装、直接在线编辑运行的平台推荐(如CodePen、JSFiddle)。无论你是新手还是进阶开发者,都能轻松掌握HTML5网页制作、响应式布局与交互功能开发,零配置开启高效前端编程之旅!

2

2025.12.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 6.4万人学习

Excel 教程
Excel 教程

共162课时 | 10.3万人学习

PHP基础入门课程
PHP基础入门课程

共33课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号