1. 原则

推荐以符合以下原则的方式编写模板化控件:

  • 选择合适的父类: 选择合适的父类可以节省大量的工作,从UWP自带的控件中选择父类是最安全的做法,通常的选择是Control、ContentControl、ItemsControl,也可以选择从RangeBase、Selector中。
  • 代码和UI分离: 通常控件的开发者不能控制最终用户怎么重写ControlTemplate,尽量做到代码和UI分离可以避免更多的异常。而且先写完所有代码,再用Blend实现UI,会比在代码和UI间交错地工作更高效。
  • 使用依赖属性: 控件的使用者会认为所有控件的属性都是可以绑定的,除非有特殊理由不要破坏这个约定俗成的规则。
  • 不要实施严格的模版约定: 模版约定指TemplatePart和TemplateVisualState,应该尽可能减少约定,在没有遵循模版约定的任何一项时也不应该引发异常,要允许ControlTemplate的开发者可以通过删除某项TemplatePart或VisualState来屏蔽某项功能。

2. 命名模式

一个控件是否好用,很大一部分取决于名称。好的命名能让使用者用起来更得心应手,坏的命名只会让代码更混淆。下面总结了UWP控件命名的一般模式:

  • 根据控件实际功能命名,譬如Button。
  • 以父类型的名字作为后缀,如RepeatButton。
  • 使用常用的后缀,如-Control、-Box、-Item、-View、-Viewer、-Bar。
  • 如果控件如现有控件功能相同,可以考虑使用Extend-、Advanced-、Simple-做前缀;也可以使用公司名做前缀,譬如ComponentOne公司的C1DataGrid。
  • 可以使用-ex做后缀,但容易和扩展方法类混淆。
  • ItemsControl派生类的子元素控件要使用父元素名称做前缀、-Item做后缀,譬如ComboBox的子元素ComboBoxItem。
  • 如果控件通过鼠标选取内容(通常会打开一个Popup),可以使用-Picker做后缀。
  • 尽量不要用-Panel做后缀,通常只有继承Panel的才会用这种方式命名,如StackPanel。但也有ControlPanel这种例外。

3. 小技巧

对于复杂的控件或控件库项目,以下技巧可能对你有帮助。

3.1 partial class

在编写模板化控件时,依赖属性最大的缺点会暴露无遗:它太复杂了。一个完整的依赖属性定义可以有20行(属性标识符、属性包装器、PropertyChangedCallback等),而且其中一部分是静态的,另外一部分不是,在类中将一个依赖属性的所有部分放在一起,还是按静态、非静态的顺序存放,这也可能引起争论。

一个好的做法是使用单独的partial 类存放所有依赖属性,具体可参考UWPCommunityToolkit的AdaptiveGridView.Properties.cs

3.2 合并资源字典

如果一个项目的模板化控件太多,Generic.xaml会异常的复杂,可以将各个控件的资源文件分开存放,再在Generic.xaml中合并它们。具体可参考UWPCommunityToolkit的做法:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/HamburgerMenu/HamburgerMenu.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/RangeSelector/RangeSelector.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/SlidableListItem/SlidableListItem.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/ImageEx/ImageEx.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/RadialGauge/RadialGauge.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/PullToRefreshListView/PullToRefreshListView.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/RotatorTile/RotatorTile.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/BladeView/BladeView.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/GridSplitter/GridSplitter.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/MasterDetailsView/MasterDetailsView.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/ScrollHeader/ScrollHeader.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/MosaicControl/MosaicControl.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

3.3 统一管理VisualState

在控件库中,很多VisualState都是通用的,譬如Normal、Disabled、Selected,把它们全都写进一个VisualStates的类中可以方便调用。SilverlightToolkit即是使用这种方式处理:VisualStates.cs

/// <summary>
/// Names and helpers for visual states in the controls.
/// </summary>
internal static class VisualStates
{
#region GroupCommon
/// <summary>
/// Common state group.
/// </summary>
public const string GroupCommon = "CommonStates"; /// <summary>
/// Normal state of the Common state group.
/// </summary>
public const string StateNormal = "Normal"; /// <summary>
/// Normal state of the Common state group.
/// </summary>
public const string StateReadOnly = "ReadOnly"; /// <summary>
/// MouseOver state of the Common state group.
/// </summary>
public const string StateMouseOver = "MouseOver"; /// <summary>
/// Pressed state of the Common state group.
/// </summary>
public const string StatePressed = "Pressed"; /// <summary>
/// Disabled state of the Common state group.
/// </summary>
public const string StateDisabled = "Disabled";
#endregion GroupCommon
}

4. 结语

这个系列的主旨是讲解常见的模板化控件技术,希望了解这些技术后能更轻松地构造自己的控件,对理解开源控件库的代码也有一定的帮助。

职业生涯中看过很多程序员都不会写模板化控件(毕竟大部分场景使用UserControl或修改ControlTemplate就能解决),希望这个系列可以帮到想要学习模板化控件的开发者。

虽然写得很长,其实已经尽量精简文字和内容了。平时我看到很长的文章,都会“保存到Pocket”,然后就再也没读过。汲取了这个教训,这次的文章分成多篇,尽量每篇都控制在可以三五分钟内看完。

这个系列的内容有很多来自于WPF/Silverlight的经验,虽然有一些小出入,基本上可以用在WPF的自定义控件。

创建模板化控件通常意味着会被其它开发者使用,那么就应该遵守Framework Design Guidelines

如有错漏请指出。

5. 参考

控件模板

Silverlight 控件自定义

UWPCommunityToolkit

[UWP 自定义控件]了解模板化控件(10):原则与技巧的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    ContentControl是最简单的TemplatedControl,而且它在UWP出场频率很高.ContentControl和Panel是VisualTree的基础,可以说几乎所有VisualTr ...

  9. [UWP 自定义控件]了解模板化控件(5):VisualState

    1. 功能需求 使用TemplatePart实现上篇文章的两个需求(Header为空时隐藏HeaderContentPresenter,鼠标没有放在控件上时HeaderContentPresent半透 ...

随机推荐

  1. linux系统运行状态检查

    目录 1 CPU状态检查 1.1 运行时间 1.2 CPU占用率 1.3 单核占用率 2 内存状态检查 2.1 内存占用率 2.2 交换分区占用率 3 磁盘状态检查 3.1 系统磁盘容量占用率 3.2 ...

  2. 上下文管理器——with语句的实现

    前言 with语句的使用给我们带来了很多的便利,最常用的可能就是关闭一个文件,释放一把锁. 既然with语句这么好用,那我也想让我自己写的代码也能够使用with语句,该怎么实现? 下面具体介绍怎样实现 ...

  3. 点击eclipse包报错

    每次只要新建一个package包,或者鼠标选择某个package包,系统就会提示:An error has occurred. See error log for more details. org/ ...

  4. 动态、静态编译以及MD、MDd、MT、MTd编译

    本文转自:https://blog.csdn.net/u012273127/article/details/71419499 一.问题的引出 最近在VS2012中新建了一个MFC的工程,在自己电脑上运 ...

  5. python pip常用命令

    pip安装命令: pip install packagename pip显示模块版本号: pip show packagename pip卸载模块: pip uninstall packagename ...

  6. C#深度学习の----深拷贝与浅拷贝

    本人在进行编程的时候遇到一个问题,要对一个绑定的依赖属性进行赋值,改变属性中的某一部分,绑定的目标上的所有值都发生了变化,着并不是我想要的,由此引出深浅拷贝的问题.(请加群交流:435226676) ...

  7. solidity学习-cryptoPunks为实例

    在这里使用cryptoPunks为实例来进行solidity的介绍,一般这些内容理解了就能够进行相对简单的智能合约的编写了,同时会添加一些我认为也十分重要的内容学习文档为http://solidity ...

  8. 617. Merge Two Binary Trees

    https://www.cnblogs.com/grandyang/p/7058935.html class Solution { public: TreeNode* mergeTrees(TreeN ...

  9. 关于pyquery小知识点

    #表示的是取html中的id元素, . 表示的是取html中的class元素. 如果是标签,就直接用标签名 而它们之间的空格,则表示嵌套关系 单冒号(:)用于CSS3伪类,双冒号(::)用于CSS3伪 ...

  10. ES5与ES6对比

    ES5与ES6对比 1. 模块引用 1.在ES5里,引入React包基本通过require进行,代码类似这样: // ES5 var React = require('react'); var { C ...