有个很惨的项目,已经一年多了,一直使用默认window的样子,版本和功能有点繁杂,一直被吐槽了很久,所以想利用最近的时间来重做一个漂亮的界面组件。既然要做那就认认真真的安排一下,首先要明确目的,就是这组控件打算做成什么样子,都有哪些功能。计划如下:
一、公共的控件类,并且方便扩展到其它项目
二、控件的外观素材与控件本身分离,并使外观效果更易于更新替换
三、支持一鍵换肤功能,包括在运行时与设计时
四、做一个控件皮肤风格设计器并支持多种部署方案
五、笔者是完美主义者,一定要完美
但很不幸,本人的平面设计能力基本为零,别人都说我的审美观有异于常人,所以要的把这组控件全部做成贴图的形式,这样就可以让别人来改善我审美上的缺陷了。
码了这么多年,这还是第一次做UI方面的设计,总有种前途多舛的感脚。
一、继承关系。
当从IDE中创建一个新的用户控件时,自动生成的代码是继承自UserControl,这里有必要说明一下这些类型的继承关系。
Component的类,只要是继承它的类都会显示在IDE的工具箱内,而它又继承自MarshalByRefObject,这说明控件可以被序列化和跨域访问。
Control继续自Component,它封装了窗口的特性与相关功能,这决定了Control与其子类可以从工具箱放到设计器上显示出用户界面,也就是我们说的UI,从此每一个控件其实就是一个window窗体了,而像Menu,Timer,ToolBar,FileDialog等这些直接继续自Component的控件来说,它们只能显示在设计器下面,因为没有UI。
ScrollableControl继续自Control,顾名思义,可滚动的控件,它封装了窗体的滚动,内外边距自动停靠等功能。
ContainerControl继续自ScrollableControl,从此处开始有了容器的概念,它封装了控件间的层次与部分焦点功能。
UserControl继续自ContainerControl,这样的一个关系,说明创建用户控件是可以实现滚动与容的功能,还兼有标准Control的那些大量的属性与事件。
当然最简单和最常见的作法是直接从标准的winform控件中派生新类,但这样只是在改旧,而不能生新,你无法在一个按钮上去做一个滚动内容的效果,而这是在flash和游戏中很常见的东西,所以基于这些种种还未想到的复杂性,笔者所以还是愿意按.NET给定的路线走,从头开始。
UserControl继承于标准控件的基类Control,使得用户自定义的控件与标准控件在运行时没有任何差别,但在设计时却有很大的不同,有些属性被override后不起作用,似乎是IDE认准了原来属性的get和set方法,对策就是隐藏这个属性,然后用新命名一个属性带替。
为了让这组控件的行为更加易于管理,应合理安排类型之间的继承关系。设计一个基类,负责所有事件响应与功能调度,在派生类中通过重写基类对应的方法来逐步完成UI功能的实现,这样一来逻辑的层级就会很清晰明快,当然作者我一定是这样认为的,但我的新手朋友却在质疑为什么要弄多的继承与重写。
二、透明色。
制作不规则窗体的重点是使窗口的BackColor与TransparencyKey相同,这样背景就镂空了,窗口的背景图就是窗口的有效范围。但等等,因为winform在所有控件和背景重绘后才进行窗口透明处理,这导致了窗口中不论是背景还是控件,在运行时只要和背景色相同就会被镂空,所以重点就要选择并牺牲一个不常用颜色给背景,笔者选择的是LightGreen。有人问我镂空了会怎样?鼠标一点就点windows桌面上的东西了呗。
window中可识别的静态位图格式中只有png支持透明色,但其它格式可以手工的滤掉一种颜色使之变透明。强烈建议把窗口的TransparencyKey作为位图的底色,这样过滤掉这个颜色后的位图就不可能再会因一点点失误让用户直接点到桌面了,只可惜那些素材都是白色底色的,无奈。
继续说透明这个概念,透明是相对性的,一个控件背景透明以后,只会显示出它父一级的背景,如果它父一级也透明就会显示再父一级,直至到窗口本身,但如果两个控件是在同一个父级容器中就不行了,要实现背景叠加的效果则必须通过重绘来实现。
三、用到的特性
DescriptionAttribute、CategoryAttribute和DisplayNameAttribute这三个都是PropertyGrid常用的,这里不作说明
DefaultEventAttribute作用于类型,定义控件的默认事件,也就是在设计时双击这个控件触发的事件
ToolboxItemAttribute作用于类型,定义控件是否在工具箱中出现,笔者的用途是让基类不出现在工具箱中,但其派生的类又可以出现
BrowsableAttribute作用于属性,定义属性是否在设计时出现在属性框中,其实定义的属性默认都是会显示的,所以它的作用基本是用来隐藏在设计时不想被修改的属性,包括父级属性
DefaultValueAttribute作用于属性,定义属性的默认值,此特性并不会初始化属性的值,它的作用:如果属性不等于此默认值时,在属性框中显示为粗体,并在设计时的代码生成器中会生成相应属性赋值的语句,如果为了代码更加有效率的话,还是使用它吧
TypeConverterAttribute作用于属性,当属性值不是system常用的值类型时,定义设计器赋值时使用的类型转换器,几种默认的枚举类型系统已有定义,在System.Drawing.Design命名空间中,例如Color
EditorAttribute作用于属性,属性框中属性值的高级编辑的接口,像属性框中可弹出的颜色选择框,文件选择框,图像源等等都是这个特性在起作用
DesignOnlyAttribute作用于属性,乍一看感觉没什么用,但实际作用非常大。在某些时候设计器上显示的属性值只是想显示效果,而在编译时并不想生成属性赋值的代码,这时它就有用了
DesignerAttribute作用于类型,为某些控件提供独立的设计器,从而达到复杂UI在设计时更加方便快捷,例如TabControl控件就是独立的设计器
四、事件响应
通常对于控件自身的事件应该使用正常的事件响应,但笔者还是建议重写了控件的那些protected的方法,这些方法实际就是用来触发事件的,重写它可以达到两个目的:一在事件委托广播事件之前运行自己的代码,二可以直接屏蔽事件的触发。
五、位图产生的问题
Image和继续自它的Bitmap是主要操作的位图对象,毫无疑问,它们都是非托管API的封装类型。在大量图元操作时,最容易发生的就是内存泄露了,很不幸就算是全都使用dispose方法也不能马上释放内存,这在不同的目标平台上会有不同的延迟。使用过多的位图组合的控件,将直接导致性能上的损失,不论是贴背景还是直接重绘控件,都无法避免资源管理与释放的问题。
在设计时,背景图形被伸拉是很常见的,可有些背景我们只希望图像中间部分被伸拉,边框仍然保持纤细,为了应对,笔者将背景砍成九块,即四角四边与中间,效果是这样,四角不参与伸拉,四边只单向伸拉,而中间部分则按大小伸拉。如果你要在控件中放九个piciturebox就太out了,所以我写了一个名为ImageCombine的方法按控件大小伸拉并拼接位图,这样一来,圆角的矩形背景就不会因为伸拉而让四角变大了。
=================================================================
一晃已经是2019年了,偶然回来看看,发现这个五年前没有发布的草稿 posted @ 2014-11-18 04:07,但很可惜它没有后续,因为一些本人的原因吧,具体是什么也忘了,总之是离开了当时的公司。真挺可惜的,还是发出来吧!