[UWP 自定义控件]了解模板化控件(2):模仿ContentControl

ContentControl是最简单的TemplatedControl,而且它在UWP出场频率很高。ContentControl和Panel是VisualTree的基础,可以说几乎所有VisualTree上的UI元素的父节点中总有一个ContentControl或Panel。

因为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>

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏耕耘实录

虚拟化迁移,你需要特别注意的几个问题

版权声明:本文为耕耘实录原创文章,各大自媒体平台同步更新。欢迎转载,转载请注明出处,谢谢。

25030
来自专栏Debian社区

2016 年 Go 语言使用习惯调查

Go 语言官方博客公布了 2016 年 Go 语言使用调查。在 3,595 名被调查者中,89% 称他们在工作中或工作之外用 Go 编程;63% 称他们的工作是...

8630
来自专栏Debian社区

Linux: Debian 9 Stretch可能将不支持Secure Boot

Debian 团队宣布 Debian 9 Stretch 在发布时将不太可能会支持 Secure Boot。Debian 9 的代码在今年 2 月冻结,之后开发...

13220
来自专栏耕耘实录

Windows10更新后,解决C盘空间占用过大的方法

本人有开启自动更新的习惯,最近几天,极客君的电脑动不动就更新,每次都更新很久,并且更新完之后还要求重启呢!到今天为止,更新总算是消停下来了,打开更新完的系统极客...

40980
来自专栏耕耘实录

Linux修改用户名及相关信息(包括root)

版权声明:本文为耕耘实录原创文章,各大自媒体平台同步更新。欢迎转载,转载请注明出处,谢谢

64320
来自专栏耕耘实录

巧用OpenSSL完成md2、md4、md5、rmd160、sha、sha1等的验证

版权声明:本文为耕耘实录原创文章,各大自媒体平台同步更新。欢迎转载,转载请注明出处,谢谢

11030
来自专栏耕耘实录

Linux下定时任务(系统任务调度、用户任务调度)crontab使用详解

版权声明:本文为耕耘实录原创文章,各大自媒体平台同步更新。欢迎转载,转载请注明出处,谢谢

20710
来自专栏Debian社区

Proxmox VE 5.0 Beta1 发布,基于 Debian 9 Stretch

Proxmox 5.0 Beta 1 “Virtual Environment” 发布了,新版基于 Debian 9 “Stretch”,采用了 Linux 4...

12820
来自专栏Debian社区

Greg Kroah-Hartman批评Ubuntu发行版

Ubuntu根本就是个祸害开源社区发行版。资本家是“无利不起早”的。“新人用Ubuntu系列是最不明智的选择,因为很难利用到别人的经验。Ubuntu修改了太多东...

25750
来自专栏耕耘实录

两种方法实现Linux不活动用户登录超时后自动登出

版权声明:本文为耕耘实录原创文章,各大自媒体平台同步更新。欢迎转载,转载请注明出处,谢谢。

24420

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励