[UWP]了解模板化控件(2):模仿ContentControl
2017-03-24 15:43
483 查看
ContentControl是最简单的TemplatedControl,而且它在UWP出场频率很高。ContentControl和Panel是VisualTree的基础,可以说几乎所有VisualTree上的UI元素的父节点中总有一个ContentControl或Panel。
因为ContentControl很简单,如果只实现ContentControl最基本功能的话很适合用来做TemplatedControl的入门。这次的内容就是模仿ContentControl实现一个模板化控件MyContentControl,直接继承自Control。
MyContentControl只实现ContentControl两个最常用的属性:Content和ContentTemplate。两个都需要使用依赖属性,这样才可以使用Binding和下面会用到的TemplateBinding。
通常重要的属性都会定义一个通知属性值变更的virtual方法给派生类使用,如这里的
值得一提的是Content属性的类型是Object,这样Content中既可以放文字,也可以放图片、Panel等元素。在UWP中如无特殊需求,Content、Header、Title等内容属性最好都是Object类型,这样更方便扩展,例如可以在Header放一个Checkbox,这是很常见的做法。
将Themes/Generic.xaml中TargetType="local:MyContentControl"的Style改写成上述XAML。
UWP通过ControlTemplate定义控件的外观。在MyContentControl中,ControlTemplate只有一个元素ContentPresenter,它使用TemplateBinding绑定到自己所在的MyContentControl的公共属性。对经常使用ControlTemplate的开发者来说ContentPresenter和TemplateBinding都不是陌生的概念。
TemplateBinding只能用在ControlTemplate中。
TemplateBinding的源和目标属性都必须是依赖属性。
TemplateBinding不能使用TypeConverter,所以源属性和目标属性必须为相同的数据类型。
通常在ContentPresenter上使用TemplateBinding的属性不会太多,因为很大一部分Control的属性都是可属性值继承的,即默认使用VisualTree上父节点所设置的属性值,譬如字体属性(如FontSize、FontFamily)、DataContext等。
除了可属性值继承的属性,需要适当地将ControlTemplate中的元素属性绑定到所属控件的属性,例如
使用MyContentControl的XAML如上所示,但看起来和ContentControl不同,多了
在MyContentControl使用这个Attribute,UWP在解释XAML时,会将XAML的内容识别为MyContentControl的Content属性。除了可以省略两行XAML外,ContentPropertyAttribute还有指出类的主要属性的作用。譬如Panel添加了
添加ContentPropertyAttribute后,使用MyContentControl的XAML和ContentControl就基本一致了。
因为ContentControl很简单,如果只实现ContentControl最基本功能的话很适合用来做TemplatedControl的入门。这次的内容就是模仿ContentControl实现一个模板化控件MyContentControl,直接继承自Control。
1. 定义属性
/// <summary> /// 获取或设置Content的值 /// </summary> public object Content { get { return (object)GetValue(ContentProperty); } set { SetValue(ContentProperty, value); } } /// <summary> /// 标识 Content 依赖属性。 /// </summary> public static readonly DependencyProperty ContentProperty = DependencyProperty.Register("Content", typeof(object), typeof(MyContentControl), new PropertyMetadata(null, OnContentChanged)); private static void OnContentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { MyContentControl target = obj as MyContentControl; object oldValue = (object)args.OldValue; object newValue = (object)args.NewValue; if (oldValue != newValue) target.OnContentChanged(oldValue, newValue); } protected virtual void OnContentChanged(object oldValue, object newValue) { } /// <summary> /// 获取或设置ContentTemplate的值 /// </summary> public DataTemplate ContentTemplate { get { return (DataTemplate)GetValue(ContentTemplateProperty); } set { SetValue(ContentTemplateProperty, value); } } /// <summary> /// 标识 ContentTemplate 依赖属性。 /// </summary> public static readonly DependencyProperty ContentTemplateProperty = DependencyProperty.Register("ContentTemplate", typeof(DataTemplate), typeof(MyContentControl), new PropertyMetadata(null, OnContentTemplateChanged)); private static void OnContentTemplateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { MyContentControl target = obj as MyContentControl; DataTemplate oldValue = (DataTemplate)args.OldValue; DataTemplate newValue = (DataTemplate)args.NewValue; if (oldValue != newValue) target.OnContentTemplateChanged(oldValue, newValue); } protected virtual void OnContentTemplateChanged(DataTemplate oldValue, DataTemplate newValue) { }
MyContentControl只实现ContentControl两个最常用的属性:Content和ContentTemplate。两个都需要使用依赖属性,这样才可以使用Binding和下面会用到的TemplateBinding。
通常重要的属性都会定义一个通知属性值变更的virtual方法给派生类使用,如这里的
protected virtual void OnContentChanged(object oldValue, object newValue)。为了可以定义virtual方法,要移除类的sealed关键字。
值得一提的是Content属性的类型是Object,这样Content中既可以放文字,也可以放图片、Panel等元素。在UWP中如无特殊需求,Content、Header、Title等内容属性最好都是Object类型,这样更方便扩展,例如可以在Header放一个Checkbox,这是很常见的做法。
2. 实现外观
2.1 DefaultStyle
<Style TargetType="local:MyContentControl"> <Setter Property="HorizontalContentAlignment" Value="Left" /> <Setter Property="VerticalContentAlignment" Value="Top" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:MyContentControl"> <ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> </ControlTemplate> </Setter.Value> </Setter> </Style>
将Themes/Generic.xaml中TargetType="local:MyContentControl"的Style改写成上述XAML。
UWP通过ControlTemplate定义控件的外观。在MyContentControl中,ControlTemplate只有一个元素ContentPresenter,它使用TemplateBinding绑定到自己所在的MyContentControl的公共属性。对经常使用ControlTemplate的开发者来说ContentPresenter和TemplateBinding都不是陌生的概念。
2.2 ContentPresenter
ContentPresenter用于显示内容,默认绑定到ContentControl的Content属性。基本上所有ContentControl中都包含一个ContentPresenter。ContentPresenter直接从FrameworkElement派生。2.3 TemplateBinding
用于单向绑定ControlTemplate所在控件的功能属性,例如Margin="{TemplateBinding Padding}"几乎等效于Margin="{Binding Margin,RelativeSource={RelativeSource Mode=TemplatedParent},Mode=OneWay}",相当于一种简化的写法。但它们之间有如下不同:TemplateBinding只能用在ControlTemplate中。
TemplateBinding的源和目标属性都必须是依赖属性。
TemplateBinding不能使用TypeConverter,所以源属性和目标属性必须为相同的数据类型。
通常在ContentPresenter上使用TemplateBinding的属性不会太多,因为很大一部分Control的属性都是可属性值继承的,即默认使用VisualTree上父节点所设置的属性值,譬如字体属性(如FontSize、FontFamily)、DataContext等。
除了可属性值继承的属性,需要适当地将ControlTemplate中的元素属性绑定到所属控件的属性,例如
Margin="{TemplateBinding Padding}",这样可以方便控件的使用者通过属性调整UI。
2.4 通过Setter改变默认值
通常从父类继承而来的属性不会在构造函数中设置默认值,而是在DefaultStyle的Setter中设置默认值。MyContentControl为了将HorizontalContentAlignment改为Left而在Style添加了Property="HorizontalContentAlignment"的Setter。2.5 ContentPropertyAttribute
<local:MyContentControl> <local:MyContentControl.Content> <Rectangle Height="100" Width="100" Fill="Red" /> </local:MyContentControl.Content> </local:MyContentControl>
使用MyContentControl的XAML如上所示,但看起来和ContentControl不同,多了
local:MyContentControl.Content这行。解决办法是添加Windows.UI.Xaml.Markup.ContentPropertyAttribute到MyContentControl上。
[ContentProperty(Name = "Content")] public class MyContentControl : Control
在MyContentControl使用这个Attribute,UWP在解释XAML时,会将XAML的内容识别为MyContentControl的Content属性。除了可以省略两行XAML外,ContentPropertyAttribute还有指出类的主要属性的作用。譬如Panel添加了
[ContentProperty(Name = "Children")],TextBlock添加了
[ContentProperty(Name = "Inlines")]。
添加ContentPropertyAttribute后,使用MyContentControl的XAML和ContentControl就基本一致了。
<local:MyContentControl> <Rectangle Height="100" Width="100" Fill="Red" /> </local:MyContentControl>
相关文章推荐
- [UWP]了解模板化控件(5.2):UserControl vs. TemplatedControl
- [UWP]了解模板化控件(5.2):UserControl vs. TemplatedControl
- [UWP]了解模板化控件(5.2):UserControl vs. TemplatedControl
- [UWP 自定义控件]了解模板化控件(2):模仿ContentControl
- [UWP]了解模板化控件(2):模仿ContentControl
- [UWP]了解模板化控件(5.1):TemplatePart vs. VisualState
- [UWP]了解模板化控件(5.1):TemplatePart vs. VisualState
- [UWP]了解模板化控件(6):使用附加属性
- [UWP]了解模板化控件(4):TemplatePart
- [UWP]了解模板化控件(5):VisualState
- [UWP]了解模板化控件(7):支持Command
- [UWP]了解模板化控件(8):ItemsControl
- [UWP]了解模板化控件(7):支持Command
- [UWP]了解模板化控件(4):TemplatePart
- [UWP]了解模板化控件(8):ItemsControl
- [UWP]了解模板化控件(9):UI指南
- [UWP]了解模板化控件(5):VisualState
- [UWP]了解模板化控件(9):UI指南
- [UWP]了解模板化控件(10):原则与技巧
- UWP开发入门(十八)——使用ContentControl减少页面元素数量