[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