当前位置:

首页 > 软件教程 > UWP自定义控件:TemplatePart与VisualState解析

UWP自定义控件:TemplatePart与VisualState解析

Windows 11
Windows 11

Windows 11 是微软全新操作系统,现代化界面,流畅操作体验,适用于学习、工作与日常生活。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

立即下载
¥159
Windows 2026-08-22
正版软件 Windows 操作系统 Windows 11 Windows下载

在前两篇文章中,我们分别使用了TemplatePart和VisualState的方法实现了相同的功能,其中VisualState显然更具灵活性。在这种情况下,我通常更倾向于使用VisualState。然而,在实际应用中,这两种实现方式并不是互斥的,许多模板化控件会同时使用这两种方法。使用VisualState的好处包括:代码和UI分离,使得控件扩展更灵活。可以使用Blend轻松实现动画。尽管VisualState有诸多优势,但并不是说所有功能都必须使用VisualState实现。以下情况我会选择使用Tem

本文目录

    在前两篇文章中,我们分别使用了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需要有良好的扩展能力,可以尝试使用这种方式。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    软件教程 Windows
    相关文章 更多
    电脑黑屏重启怎么看日志?查看异常重启原因的方法
    电脑黑屏重启怎么看日志?查看异常重启原因的方法

    遇到电脑黑屏重启不知从何下手?通过Windows内置的事件查看器和BlueScreenView工具,精准定位系统崩溃前的最后记录。本文详解查看关键错误ID、分析Dump文件及常见代码含义,助你快速解决异常重启问题。

    电脑黑屏按什么键恢复?常用亮屏操作与排查方法
    电脑黑屏按什么键恢复?常用亮屏操作与排查方法

    遇到电脑黑屏不知道按什么键?本文详解Windows和Mac系统的常用恢复快捷键,包括唤醒屏幕、切换投影模式及重置显卡驱动,并提供基础的硬件连接检查方法,助你快速恢复显示。

    电脑黑屏重启是什么原因?日志查看与排查方法
    电脑黑屏重启是什么原因?日志查看与排查方法

    遇到电脑黑屏自动重启不要慌,本文教你如何使用Windows事件查看器查找崩溃日志,分析Kernel-Power和BugCheck错误,并通过排除法解决驱动冲突或硬件过热问题。

    电脑蓝屏显示“你的电脑遇到问题需要重新启动”怎么解决
    电脑蓝屏显示“你的电脑遇到问题需要重新启动”怎么解决

    详细解析Windows蓝屏错误“你的电脑遇到问题需要重新启动”的解决方法,包括记录停止代码、进入安全模式、卸载冲突更新及硬件检测步骤。

    电脑蓝屏怎么办?重启无效时的排查与恢复方法
    电脑蓝屏怎么办?重启无效时的排查与恢复方法

    遇到电脑蓝屏且重启无效的情况?本教程详解如何进入安全模式、卸载冲突驱动及使用系统还原点,快速定位并解决导致蓝屏的核心问题,避免数据丢失。

    电脑蓝屏自动修复失败怎么办?常见原因与解决方法
    电脑蓝屏自动修复失败怎么办?常见原因与解决方法

    遇到电脑蓝屏且自动修复循环失败?本教程详解如何进入安全模式、卸载近期更新及使用CMD修复引导记录,助你跳出修复死循环。

    电脑黑屏只有一个鼠标箭头怎么办?原因与解决方法
    电脑黑屏只有一个鼠标箭头怎么办?原因与解决方法

    遇到电脑黑屏但鼠标能动?这通常是Windows资源管理器崩溃或显卡驱动冲突导致。本文提供无需重装系统的快速修复步骤,包括重启explorer.exe、安全模式排查及驱动更新方法。

    电脑蓝屏怎么解决?从错误代码到无法开机的处理方法
    电脑蓝屏怎么解决?从错误代码到无法开机的处理方法

    遇到电脑蓝屏不要慌。本文详解如何记录蓝屏错误代码,通过安全模式卸载问题驱动,使用命令提示符修复系统文件,以及在无法进入桌面时的进阶处理方法。适用于Windows 10/11用户。

    电脑黑屏只有鼠标箭头怎么办?原因与修复方法
    电脑黑屏只有鼠标箭头怎么办?原因与修复方法

    电脑开机后屏幕全黑仅显示鼠标箭头?这通常是Windows资源管理器崩溃或驱动问题。本文提供无需重装系统的快速修复方案,包括重启Explorer进程、安全模式排查及驱动更新方法。

    电脑蓝屏记录在哪里查看?日志打开与代码查询方法
    电脑蓝屏记录在哪里查看?日志打开与代码查询方法

    遇到电脑蓝屏不知道原因?本文详解如何在Windows中通过事件查看器和专用工具查看蓝屏日志,解读停止代码,快速定位硬件或驱动问题。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。

    即将离开本站
    您即将前往第三方网站,请确认是否继续?