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. 10 个实用技巧,让 Finder 带你飞

    Finder 是 Mac 电脑的系统程序,有的功能类似 Windows 的资源管理器.它是我们打开 Mac 首先见到的「笑脸」,有了它,我们可以组织和使用 Mac 里的几乎所有东西,包括应用程序.文件 ...

  2. Visual Studio 原生开发的10个调试技巧(二)

    原文:Visual Studio 原生开发的10个调试技巧(二) 我以前关于 Visual Studio 调试技巧的文章引起了大家很大的兴趣,以至于我决定分享更多调试的知识.以下的列表中你可以看到写原 ...

  3. ★10 个实用技巧,让Finder带你飞~

    10 个实用技巧,让 Finder 带你飞 Finder 是 Mac 电脑的系统程序,有的功能类似 Windows 的资源管理器.它是我们打开 Mac 首先见到的「笑脸」,有了它,我们可以组织和使用 ...

  4. ★10 个实用技巧,让Finder带你飞~

    10 个实用技巧,让 Finder 带你飞 Finder 是 Mac 电脑的系统程序,有的功能类似 Windows 的资源管理器.它是我们打开 Mac 首先见到的「笑脸」,有了它,我们可以组织和使用 ...

  5. 使用google搜索时的10个小技巧!

    为大家分享一些google的技巧,很多工作了好几年的同学还不知道如何高效的利用这些技巧,希望同学们掌握!此为google的技巧,百度现在也基本上都实现了这些功能.   使用搜索引擎的10个搜索技巧   ...

  6. (译)MySQL的10个基本性能技巧

    原文出处:https://www.infoworld.com/article/3210905/sql/10-essential-performance-tips-for-mysql.html MySQ ...

  7. (译)关于使用Eclipse Memory Analyzer的10点小技巧

    作者 Rave_Tian 2016.02.01 17:56* 字数 2988 阅读 520评论 0喜欢 0 分析和理解应用的内存使用情况是开发过程中一项不小的挑战.一个微小的逻辑错误可能会导致监听器没 ...

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

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

  9. 【redis专题(10)】KEY设计原则与技巧

    对比着关系型数据库,我们对redis key的设计一般有以下两种格式: 表名:主键名:主键值:列名 表名:主键值:列名 在所有主键名都是id的情况下(其实我个人不喜欢这种情况,比如user表,它的主键 ...

随机推荐

  1. 万年历java

    public void showTime(){/*万年历 :  1900年1月20号是星期几?1月1号是星期一1月8号是星期一1月15号是星期一1%7 = 18%7 = 115%7 = 1★: 1. ...

  2. R学习笔记 第五篇:字符串操作

    文本数据存储在字符向量中,字符向量的每个元素都是字符串,而非单独的字符.在R中,可以使用双引号,或单引号表示字符,函数nchar用于获得字符串中的字符数量: > s='read' > nc ...

  3. update and的坑

    开发那边抛出个有意思的问题,下面的现象如何解释呢? mysql> select * from A; +------+------+ | t1 | t2 | +------+------+ | 1 ...

  4. 【python】python的正则表达式 re

    ps:本文摘自互联网,觉得结构很好,讲的也很清晰.记下,备查. 延伸阅读:python的 内建函数 和 subprocess .此文是本系列的第三篇文章了,和之前一样,内容出自官方文档,但是会有自己的 ...

  5. 【Java框架型项目从入门到装逼】第三节 - 如何用Tomcat发布web项目?

    啥叫Tomcat?有道词典是这么说的. 这个我们姑且不管,实际上呢,Tomcat是一种Web服务器,我们自己做好了一个Web项目,就可以通过Tomcat来发布.服务器呢,又分为硬件服务器和软件服务器. ...

  6. canvas动画之动态绘出六边形

    先上 demo: http://en.jsrun.net/W5iKp/show 这两天我一直在研究这个动画,花了大量的时间来想是如何实现的, 一开始我是想在进入 canvas 时按时间来用 lineT ...

  7. R-CNN论文翻译

    R-CNN论文翻译 Rich feature hierarchies for accurate object detection and semantic segmentation 用于精确物体定位和 ...

  8. 自定义控件,上图下字的Button,图片任意指定大小

    最近处在安卓培训期,把自己的所学写成博客和大家分享一下,今天学的是这个自定义控件,上图下字的Button安卓自带,但是苦于无法设置图片大小(可以在代码修改),今天自己做了一个,首先看一下效果图,比较实 ...

  9. 超级有用的Vim命令

    你是否曾经烦恼,每次编辑vim文件,想要跳到一行结尾,需要按多次右键,每次想找到某个字符的位置,都得用肉眼去观察,每次想跳到文件结尾,都要按多次向下键.现在,你不必担心这些繁杂的过程,因为我们完全可以 ...

  10. 裸板驱动总结(makefile+lds链接脚本+裸板调试)

    在裸板2440中,当我们使用nand启动时,2440会自动将前4k字节复制到内部sram中,如下图所示: 然而此时的SDRAM.nandflash的控制时序等都还没初始化,所以我们就只能使用前0~40 ...