[WPF自定义控件] 开始一个自定义控件库项目
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
递增时,MINOR
和 PATCH
将重置为零。 当 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
转载于:https://www.cnblogs.com/dino623/p/CustomControLibrary.html
[WPF自定义控件] 开始一个自定义控件库项目相关推荐
- Visual Stdio 无法直接启动带有“类库输出类型”的项目若要调试此项目,请在此解决方案中添加一个引用库项目的可执行项目。将这个可执行项目设置为启动项目!
Visual Stdio 无法直接启动带有"类库输出类型"的项目若要调试此项目,请在此解决方案中添加一个引用库项目的可执行项目.将这个可执行项目设置为启动项目! 参考文章: (1) ...
- 第一个GoogleTest(gtest)项目实验日记
第一个GoogleTest(gtest)项目实验日记 目录 零.前言... 2 一.创建一个带main函数的项目... 3 二.创建GoogleTest环境... 6 1.头文件... 6 2.编写简 ...
- 1.创建一个自定义控件
1.创建一个自定义控件 原文请看我个人博客:http://clzf.co/blog.php?id=1 这篇文章是 C#自定义控件开发 系列的第一篇文章 其实如果你看过[GDI+程序设计]这本书的话 我 ...
- qt自定义控件_Qt编写自定义控件60-声音波形图
一.前言 这个控件源自于一个音乐播放器,在写该音乐播放器的时候,需要将音频的数据转换成对应的频谱显示,采用的fmod第三方库来处理(fmod声音系统是为游戏开发者准备的革命性音频引擎,非常强大和牛逼) ...
- WPF 第三方控件主题库
WPF 第三方控件主题库 一.MaterialDesigonToolkit 开源项目:https://github.com/MaterialDesignInXAML/MaterialDesignInX ...
- WPF源码控件库《Newbeecoder.UI》轮播
轮播控件是一种强大且视觉上吸引人的方式来呈现多个数据项,本文讨论Newbeecoder.UI轮播控件的原理和一个简单的演示应用程序. 轮播控件是包含Canvas控件的 WPF 用户控件,项目控件是的子 ...
- android studio 库项目管理,在Android Studio中将现有项目转换为库项目
在模块的applicationId文件中(如果使用模块,则不是根项目!),只需替换: apply plugin: 'com.android.application' // or, if you're ...
- 13000行代码、19大技术,这位16岁高中生用C++从头到尾构建了一个机器学习库!...
作者 | 苏宓 出品 | CSDN(ID:CSDNnews) 你是从什么时候开始编程的? 据 CSDN 调研数万名开发者的数据显示,近六成的开发者表示自己写下第一行代码的年龄是在 16-20 岁间.其 ...
- 如何创建一个数据科学项目?
摘要: 在一个新的数据科学项目,你应该如何组织你的项目流程?数据和代码要放在那里?应该使用什么工具?在对数据处理之前,需要考虑哪些方面?读完本文,会让你拥有一个更加科学的工作流程. 假如你想要开始一个 ...
最新文章
- 超卖频发or商品滞销?压倒卖家的最后一根稻草竟是库存!
- FlexUnit单元测试(第三章FlexUnit事件断言)
- Win Phone 8 实现页面导航
- MySQL数据库Raid存储方案
- python中的print()、str()和repr()的区别
- iOS自动化打包之重签名导出不同证书ipa探索
- TVM:通过Python接口(AutoTVM)来编译和优化模型
- python中类怎么理解_Python中的列表理解
- 计算机的发展英语600词,程序员必备的600个英语词汇
- 【声传播】——球面波的反射
- java基础----变量与常量+作用域
- Linux---管道通信的使用
- 6.6 AdaBoost实战
- Windows远程连接Linux虚拟机图形界面
- 容器时代的DevOps部署-普元DevOps
- CAD如何快速计算面积并标注?CAD计算面积并标注
- python开发微信点餐_微信点餐平台开发 (一)
- StretchDIBits显示8位图问题
- 2017年c语言试题,2017年计算机二级C语言试题
- YUV与RGB格式转换
热门文章
- RMAN-06023: no backup or copy of datafile 6 found to restore
- [INS-20802] Oracle Net Configuration Assistant failed
- 一个不错的网络基础知识网站
- Flutter GetX 状态管理 响应式编程(三)
- mysql获取表的行号
- php 静态成员(static)抽象类(abstract)和接口(interface)
- 《Go学习笔记 . 雨痕》方法
- C#OOP之一面向对象简介
- Highcharts改Y轴的刻度值
- jsp 静态资源 打入jar