zoukankan      html  css  js  c++  java
  • [UWP 自定义控件]了解模板化控件(5.1):TemplatePart vs. VisualState

    1. TemplatePart vs. VisualState

    在前面两篇文章中分别使用了TemplatePart及VisualState的方式实现了相同的功能,其中明显VisualState的方式更灵活一些。如果遇到这种情况通常我更倾向使用VisualState。不过在实际应用中这两种实现方式并不是互斥的,很多模板化控件都同时使用这两种方式,

    使用VisualState有如下好处:

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

    并不是说VisualState好处这么多就一定要用VisualState实现所有功能,下面这些情况我会选择使用TemplatePart:

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

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

    2. TemplatePart vs. TemplateBinding

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

    2.1 使用TemplatePart

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

    <Style TargetType="local:DateTimeSelector">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:DateTimeSelector">
                    <StackPanel Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                        <CalendarDatePicker x:Name="DateElement"
                                            Margin="0,0,0,5" />
                        <TimePicker x:Name="TimeElement" />
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
    
    

    代码如下:

    [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";
    
        /// <summary>
        /// 标识 SelectedDateTime 依赖属性。
        /// </summary>
        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);
        }
    
        /// <summary>
        /// 获取或设置SelectedDateTime的值
        /// </summary>  
        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,也不能通过继承来达到目的。

    2.2. 使用TemplateBinding

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

    public class DateTimeSelector2 : Control
    {
        /// <summary>
        /// 标识 Date 依赖属性。
        /// </summary>
        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);
        }
    
        /// <summary>
        /// 标识 Time 依赖属性。
        /// </summary>
        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);
        }
    
        /// <summary>
        /// 标识 DateTime 依赖属性。
        /// </summary>
        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);
        }
    
        /// <summary>
        /// 获取或设置Date的值
        /// </summary>  
        public DateTime Date
        {
            get { return (DateTime)GetValue(DateProperty); }
            set { SetValue(DateProperty, value); }
        }
    
        /// <summary>
        /// 获取或设置Time的值
        /// </summary>  
        public TimeSpan Time
        {
            get { return (TimeSpan)GetValue(TimeProperty); }
            set { SetValue(TimeProperty, value); }
        }
    
        /// <summary>
        /// 获取或设置DateTime的值
        /// </summary>  
        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中通过绑定使用这些数据。

    <Style TargetType="local:DateTimeSelector2">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:DateTimeSelector2">
                    <StackPanel Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                        <CalendarDatePicker Margin="0,0,0,5"
                                            Date="{Binding Date,RelativeSource={RelativeSource Mode=TemplatedParent},Mode=TwoWay,Converter={StaticResource DateTimeOffsetConverter}}" />
                        <TimePicker Time="{Binding Time,RelativeSource={RelativeSource Mode=TemplatedParent},Mode=TwoWay}" />
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
    
    <Style x:Key="DateTimeSelector2CustomStyle"
           TargetType="local:DateTimeSelector2">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:DateTimeSelector2">
                    <StackPanel Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                        <DatePicker Margin="0,0,0,5"
                                    Date="{Binding Date,RelativeSource={RelativeSource Mode=TemplatedParent},Mode=TwoWay,Converter={StaticResource DateTimeOffsetConverter}}" />
                        <TimePicker Time="{Binding Time,RelativeSource={RelativeSource Mode=TemplatedParent},Mode=TwoWay}" />
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
    

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

  • 相关阅读:
    使用存储过程查询并按每页10条记录分页显示图书借阅纪录
    两个div并排 左边div宽固定 右边自适应
    java比较时间及时间的转换
    java使用commons.io的FileUtils进行文件拷贝
    实现image宽度100%,高度与宽度一致
    vue请求前的loading动画效果
    vue项目加载前空白的动画过渡效果
    element-ui和semantic-ui冲突的解决方法--局部引入semantic-ui的css
    vue使用formdata上传多个图片,springboot以文件数组的形式接受
    快速创建vuepress项目(使用vuepress写文档)
  • 原文地址:https://www.cnblogs.com/dino623/p/TemplatePartvsVisualState.html
Copyright © 2011-2022 走看看