1. 目标

我实现了一个自定义控件库,并且打算用这个控件库作例子写一些博客。这个控件库主要目标是用于教学,希望通过这些博客初学者可以学会为自己或公司创建自定义控件,并且对WPF有更深入的了解。

控件库已放在Github上,并且也以发布到NuGet

现阶段我的目标是实现一些简单的控件,由于我并不是打算重复造轮子,所以我会挑些Extended Wpf Toolkit没有的功能实现,之后再根据常用的UI模式慢慢增加各类控件和工具。(我一直在用Extended Wpf Toolkit,作为免费开源的控件库十分好用。)

因为自己很少通过VisualStudio的Toolbox添加控件,所以暂时不考虑添加工具箱支持,如有需要可以参考这篇文章

要创建一个自定义控件库只需要在VisualStudio中新建项目并选择“WPF 自定义控件库”,但创建一个项目还有很多琐碎的需要考虑的地方,这篇文章主要介绍创建一个控件库项目需要考虑的内容。

2. 命名

万事起头难,最难的就是命名,控件库的命名也烦恼了我很久。

2.1 品牌名

如果是公司的项目,直接用公司名+产品名的组合就可以,但个人的项目就要另外考虑品牌名了。

品牌名有很多地方要考虑,例如不能使用带有贬义的名称。有涉及外观印象的词也要慎用,如Aqua,给人印象就是水的、蓝色的,如果以后要为控件库设计红色的主题就会很尴尬。诺基亚当年选择Lumia作为品牌连发音都有考虑到:

“在1980年全球只有10,000左右的注册科技商标,而如今光在美国,就有超过30万这样的注册商标。”克里斯说道,为此候选名单也从最初的200个一下锐减到为数不多的几个幸存者身上。

精通各地方言(84种语言)的语言学家们围绕这些为数不多的几个幸存者们开始工作,剔除其中某些会产生歧义的单词,并排除带有在某些国家很难发音的字母如J,LR和V,和在某些语言中不存在的字母(如在波兰语中没有的Q)的单词,以确保全球绝大多数国家和地区人民都能流畅的说出这一名称。

虽然只是个控件库而已不需要考虑这么多,但容易发音还是很重要的,最后我选了“kino”,没什么意义,只是简短好读而已。

2.2 程序集名称

上面提到的Extended Wpf Toolkit,程序集的名称是Xceed.Wpf.Toolkit;而WindowsCommunityToolkit的程序集名称是Microsoft.Toolkit。对这些著名控件库来说名称和程序集的名称不一致带来的影响应该不大,但我还是倾向控件库的名称和程序集的名称一致比较好,毕竟知名度不高的情况下,或者公司内部项目多的情况下很容易产生混乱。

《.NET设计规范:约定、惯用法与模式》这本书里提到:

  • 要用公司名称作为名字控件的前缀,这样可以避免与另一家公司使用相同的名字。
  • 要用稳定的、与版本无关的产品名称作为名字空间的第二层。

那么参考Extended Wpf Toolkit的习惯,程序集的名称应该就是Kino.Wpf.Toolkit。考虑到如果以后可能还需要实现别的类库,如Kino.Uwp.Toolkit,而这两个控件库共同引用一个基础类库的话,那这个基础类库不管是叫Kino.Wpf还是Kino.Uwp都比较尴尬。所以最后还是决定Kino.Tookit.Wpf这样的顺序。

复杂的控件,如DataGrid可以单独一个程序集(参考Microsoft.Toolkit.Uwp.UI.Controls.DataGrid),但我没打算做到这么复杂,目前一个程序集就够了。

3. 目录结构

我习惯为每一个(或每一组)控件单独建立一个目录,并且将各个控件的资源文件分开存放,再在Generic.xaml中合并它们。具体可以参考WindowsCommunityToolkit的做法:


<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/HeaderedContentControl/HeaderedContentControl.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/HeaderedItemsControl/HeaderedItemsControl.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/ImageEx/RoundImageEx.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/HeaderedTextBlock/HeaderedTextBlock.xaml" />
<ResourceDictionary Source="ms-appx:///Microsoft.Toolkit.Uwp.UI.Controls/InfiniteCanvas/InfiniteCanvas.xaml" />

</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

其它:

  • Common目录,工具类放在这个目录;
  • Converters目录,实现IValueConverter接口的类都放在这个目录;
  • Assets及Assets/Images,存放图片等资源;

4. 命名空间

由于不打算把自定义控件库做得太复杂,目前所有控件都只使用Kino.Toolkit.Wpf这个命名空间。将来如果有一些高级特性或实验性质的控件,可以按照Wpf的惯例放在Kino.Toolkit.Wpf.Primitives里面。

更进一步的,可以添加如下代码指定XAML中的命名空间:

[assembly: XmlnsPrefix("https://github.com/DinoChan/Kino.Toolkit.Wpf", "kino")]
[assembly: XmlnsDefinition("https://github.com/DinoChan/Kino.Toolkit.Wpf", "Kino.Toolkit.Wpf")]
[assembly: XmlnsDefinition("https://github.com/DinoChan/Kino.Toolkit.Wpf", "Kino.Toolkit.Wpf.Primitives")]

然后在XAML中可以这样引用:

xmlns:kino="https://github.com/DinoChan/Kino.Toolkit.Wpf"

这样做的好处是可以忽略真实的命名空间,便于以后修改命名空间或API升级。

5. 版本号

程序集的版本号格式如下:

<主版本>.<次版本>.<生成号>.<修订版本>

不过平时我都没用到“修订版本”,只使用前三个。

Kino.Toolkit.Wpf则大致遵循语义化版本控制

SemVer 的最基本方法是 3 组件格式 MAJOR.MINOR.PATCH,其中:

  • 进行不兼容的 API 更改时,MAJOR 将会增加
  • 以后向兼容方式添加功能时,MINOR 将会增加
  • 进行后向兼容 bug 修复时,PATCH 将会增加

存在多处更改时,单个更改影响的最高级别元素会递增,并将随后的元素重置为零。 例如,当 MAJOR 递增时,MINORPATCH 将重置为零。 当 MINOR 递增时,PATCH 将重置为零,而 MAJOR 保持不变。

有些人喜欢用日期作为版本号,如“2019.01.01”,这样也有它的好处,而且很多时候外部版本和内部版本不是一回事。

6 .NET Framework版本

如果只是为了自己或公司创建自定义控件库,当然是根据实际用到的.NET Framework版本选择自定义控件库的版本。就我目前情况来看,我选择了4.5。

7. 代码规范

基本上遵循《.NET设计规范:约定、惯用法与模式》及.Net Core的规范,并且使用FxCop及EditorConfig协助规范代码,参考WindowsCommunityToolkit的设定(但还是有些区别,例如花括号等;后来就越做越多区别)。一些移植过来的代码会使用SuppressMessage禁止显示警告。

8. 实现原则

我希望尽可能简单的实现一些控件,通过20%的代码解决80%的问题;我更倾向于介绍一种解决问题的思路,而不是提供一个包罗万象、面面俱到的成品。而且更复杂的问题通常都是业务上的需求,保持代码简单更方便其他人修改我的代码并灵活使用。

由于ControlTemplate是很符合开放封闭原则的实现,所以能用ControlTemplate解决的自定义问题我都尽可能留给ControlTemplate解决,而不是通过添加大量属性。

以我的经验来说,添加新功能很容易,移除旧功能会被人打,新功能的添加一定要谨慎。

因为代码总是在WPF、Silverlight、UWP之间移植来移植去,所以我一直更倾向于使用兼容性较好的方案,例如如果使用VisualState的工作量和ControlTemplate.Triggers差不多我就会使用VisualState实现(不过通常ControlTemplate.Triggers都会简单很多)。

不会添加在操作上有“独特创意”的控件。

9. 结语

Kino.Toolkit.Wpf的初衷毕竟是自己用及教学,没有通过充分的测试,如果发现严重的Bug请协助我修复。

按道理所有控件应该都不会拒绝MVVM,不过Sample里面没有用到MVVM模式,如果发现对MVVM不够友好的部分请告知。

示例代码没有使用MVVM模式,这是因为对控件的示例来说MVVM并不是那么直观,一般WPF的教材也都是使用CodeBehind的方式。

最后提一句,对于太过复杂的控件,能让公司花钱买的就尽量花钱买。

10. 引用

Github

NuGet

[WPF 自定义控件]开始一个自定义控件库项目的更多相关文章

  1. Visual Stdio 无法直接启动带有“类库输出类型”的项目若要调试此项目,请在此解决方案中添加一个引用库项目的可执行项目。将这个可执行项目设置为启动项目!

    j解决方法:项目-属性-应用程序-输出类型-Windows应用程序

  2. 使用 Flex 库项目---打包swc

    来源:http://help.adobe.com/zh_CN/flashbuilder/using/WSe4e4b720da9dedb5-1a92eab212e75b9d8b2-7ffe.html   ...

  3. [AIR] NativeExtension在IOS下的开发实例 --- Flex库项目的创建(二)

    来源:http://bbs.9ria.com/thread-102038-1-1.html 上一章,我已经介绍了如果创建IOS库文件,并定义了两个方法ShowIconBadageNumber和Init ...

  4. Qt编写自定义控件插件开放动态库dll使用(永久免费)

    这套控件陆陆续续完善了四年多,目前共133个控件,除了十几个控件参考网友开源的代码写的,其余全部原创,在发布之初就有打算将动态库开放出来永久免费使用,在控件比较完善的今天抽了半天时间编译了多个qt版本 ...

  5. WPF 走马灯 文字滚动 自定义控件

    原文:WPF 走马灯 文字滚动 自定义控件 /// <summary> /// Label走马灯自定义控件 /// </summary> [ToolboxBitmap(type ...

  6. Android 使用库项目时的一个特殊tip

    前提: 项目A作为库项目被项目B引用,但是项目A中有自定义的控件和自定义的属性,当在项目B中使用自定义的属性时,编译时就会直接报错:No resource identifier found for a ...

  7. 示例:自定义WPF底层控件UI库 HeBianGu.General.WpfControlLib V2.0版本

    原文:示例:自定义WPF底层控件UI库 HeBianGu.General.WpfControlLib V2.0版本 一.目的:封装了一些控件到自定义的控件库中,方便快速开发 二.实现功能: 基本实现常 ...

  8. 【转载】写一个js库需要怎样的知识储备和技术程度?

    作者:小爝链接:https://www.zhihu.com/question/30274750/answer/118846177来源:知乎著作权归作者所有,转载请联系作者获得授权. 1,如何编写健壮的 ...

  9. 创建你的第一个JavaScript库

    是否曾对Mootools的魔力感到惊奇?是否有想知道Dojo如何做到那样的?是否对jQuery感到好奇?在这个教程中,我们将了解它们背后的东西并且动手创建一个超级简单的你最喜欢的库. 我们其乎每天都在 ...

随机推荐

  1. Instrument API介绍

    1. Instrumentation介绍  JVMTI(JVM Tool Interface)是 Java 虚拟机所提供的 native 编程接口,是 JVMPI(Java Virtual Machi ...

  2. 【CHRIS RICHARDSON 微服务系列】事件驱动的数据管理-5

    编者的话 |本文来自 Nginx 官方博客,是「Chris Richardson 微服务」系列的第五篇文章.第一篇文章介绍了微服务架构模式,并且讨论了使用微服务的优缺点:第二和第三篇描述了微服务架构模 ...

  3. minicom配置1500000波特率

    背景 项目需求,得用1500000波特率进行,即1.5M的波特率进行串口通信. 最开始以为minicom不支持,因为第一眼在配置界面的选项中没看见.后来发现其实是支持的 方式一 启动时带参数 -b 1 ...

  4. Python基础-day01-9

    变量的命名 目标 标识符和关键字 变量的命名规则 0.1 标识符和关键字 1.1 标识符 标示符就是程序员定义的 变量名.函数名 名字 需要有 见名知义 的效果,见下图: 标示符可以由 字母.下划线 ...

  5. c++-zoo动物园

    面向对象抽象类写动物园 animal animal.h #pragma once #define _CRT_SECURE_NO_WARNINGS #include <iostream> u ...

  6. 分布式事务之解决方案(XA和2PC)

    3. 分布式事务解决方案之2PC(两阶段提交) 针对不同的分布式场景业界常见的解决方案有2PC.TCC.可靠消息最终一致性.最大努力通知这几种. 3.1. 什么是2PC 2PC即两阶段提交协议,是将整 ...

  7. angular8 导出excel文件

    angular package 1.xlsx npm install xlsx --save 2.file-saver npm install file-saver --save npm instal ...

  8. 开源资产管理系统Snipe-IT

    CentOS7安装IT资产管理系统Snipe-IT介绍资产管理工具Github:https://github.com/snipe/snipe-it官网:https://snipeitapp.com/D ...

  9. SAP B1:如何在水晶报表中插入二维码

    动态二维码API接口地址:http://www.liantu.com/api.php?text=x备注: 动态网址内可自定义相应的字段拼接(如图5为 [批号]+[质检员]字段) 若API接口链接失效, ...

  10. 'OracleInternal.MTS.DTCPSPEManager' 类型初始值设定项引发异常

    环境:VS2010,.NET Framework 4.0,Oracle.ManagedDataAccess    在最近做一个项目中,用到了Oracle数据库,使用Oracle.ManagedData ...