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>

[UWP 自定义控件]了解模板化控件(2):模仿ContentControl的更多相关文章

  1. UWP 自定义控件:了解模板化控件 系列文章

    UWP自定义控件的入门文章 [UWP 自定义控件]了解模板化控件(1):基础知识 [UWP 自定义控件]了解模板化控件(2):模仿ContentControl [UWP 自定义控件]了解模板化控件(2 ...

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

    UWP的UI主要由布局容器和内容控件(ContentControl)组成.布局容器是指Grid.StackPanel等继承自Panel,可以拥有多个子元素的类.与此相对,ContentControl则 ...

  3. [UWP 自定义控件]了解模板化控件(8):ItemsControl

    1. 模仿ItemsControl 顾名思义,ItemsControl是展示一组数据的控件,它是UWP UI系统中最重要的控件之一,和展示单一数据的ContentControl构成了UWP UI的绝大 ...

  4. [UWP 自定义控件]了解模板化控件(3):实现HeaderedContentControl

    1. 概述 来看看这段XMAL: <StackPanel Width="300"> <TextBox Header="TextBox" /&g ...

  5. [UWP 自定义控件]了解模板化控件(10):原则与技巧

    1. 原则 推荐以符合以下原则的方式编写模板化控件: 选择合适的父类:选择合适的父类可以节省大量的工作,从UWP自带的控件中选择父类是最安全的做法,通常的选择是Control.ContentContr ...

  6. [UWP 自定义控件]了解模板化控件(1):基础知识

    1.概述 UWP允许开发者通过两种方式创建自定义的控件:UserControl和TemplatedControl(模板化控件).这个主题主要讲述如何创建和理解模板化控件,目标是能理解模板化控件常见的知 ...

  7. [UWP 自定义控件]了解模板化控件(4):TemplatePart

    1. TemplatePart TemplatePart(部件)是指ControlTemplate中的命名元素.控件逻辑预期这些部分存在于ControlTemplate中,并且使用protected ...

  8. [UWP 自定义控件]了解模板化控件(5.2):UserControl vs. TemplatedControl

    1. UserControl vs. TemplatedControl 在UWP中自定义控件常常会遇到这个问题:使用UserControl还是TemplatedControl来自定义控件. 1.1 使 ...

  9. [UWP 自定义控件]了解模板化控件(9):UI指南

    1. 使用TemplateSettings统一外观 TemplateSettings提供一组只读属性,用于在新建ControlTemplate时使用这些约定的属性. 譬如,修改HeaderedCont ...

随机推荐

  1. linux中使用nfs共享文件

    NFS需要使用远程过程调用 (RPC),也就是说,我们并不是只要启动NFS, 还需要启动RPC这个服务 服务器端 CentOS 7.4 ip:172.16.0.1 共享/tmp目录 共享/data目录 ...

  2. [HDFS_2] HDFS 的 Shell 操作

    0. 说明 在 Shell 下完成对 HDFS 的增删改查操作 1. 在 Shell 下完成对 HDFS 的增删改查操作 [1.0 查看帮助] [centos@s101 ~]$ hdfs dfs -h ...

  3. docker修改容器gogs时区时间

    问题描述: 公司内部搭建了一个gogs-git,是用docker部署的,但是发现提交的代码什么的时间跟服务器时间不一致 提交上去的世界是UTC时间不是中国的时间CST,相当于慢了8个小时 1.dock ...

  4. Django应用:学习日志网站

    目录 一.创建虚拟环境(Windows) 二.创建项目 三.创建应用程序 四.创建网页:学习笔记主页 五.创建其他网页 六.用户输入数据 七.用户账户 八.让用户拥有自己的数据 九.设置应用程序样式 ...

  5. PHP实现简单下载功能

    PHP实现简单下载 PHP文件为download.php,供下载的文件为1.jpg. <?php $filename="1.jpg"; if(!file_exists($fi ...

  6. 使用Gitkraken进行其他Git操作

    使用Gitkraken进行其他Git操作 查看某次 commit 的文件改动 使用 Gitkraken 能非常方便的看到任意一次的 commit 对项目文件的改动. 具体操作是:在树状分支图上单击某个 ...

  7. <20190103>别傻了,一些 新的技术注定只适合新人

    故障现象:    用vmware跑的虚拟机不兼容 某银行网银的U盾. 插入usb口后无法识别U盾, 解决过程: 1  更换2台各自不同电脑, 使用前置USB口, 后置USB口.  故障依旧. 2  使 ...

  8. esp8266(1) 手机+Arduino+esp8266通信

    ESP8266 Android与Arduino通信 功能描述: 1 Arduino上电,它通过软串口(Arduino的 2号和3号脚)发送命令,配置espson8266为 AP模式,wifi名 DDD ...

  9. Arduino 433 + 串口

    http://www.freebuf.com/articles/wireless/105398.html /*本作品使用的例程中包含RCSwitch库文件用于信号的解码和编码发送*/ #include ...

  10. .Net修改网站项目调试时的虚拟目录(未验证)

    有些项目需要在IIS发布的时候,将网站发布到虚拟目录,为了保持调试和发布的路径同一,一般会修改VS调试的虚拟目录 一.Web应用程序 Web应用程序的修改方式非常简单,在解决方案资源管理器->项 ...