---------------------------------------------------------------------------------------------------
贫民级换肤控件skinsharp的开发历程
SkinSharp是小生一个人独立开发的换肤产品。开发这个产品的原因和绝大多数朋友一样,买不起skin++。由于对ui非常热爱,曾经毕业时还想去skin++去工作,难耐他们老板看不上咱。后来一切证实没去是非常正确的。
因为对这方面非常感兴趣,索性就自力更生吧。 关于软件换肤原理其实非常简单,就是hook + subclass, 替换窗口过程,自己重写界面绘制,基本上就是响应WM_PAINT, WM_NCPAINT消息。但难点就在绘制的处理上,Windows标准控件那么多,每个控件的表现形式可不一样。所以基本上所有的工作都集中在控件的消息响应上了。其实,标准控件是很好做的,捧一本msdn基本都可以搞定。难点都集中在滚动条,菜单换肤上。市面上换肤产品多如牛毛,但真正滚动条和菜单换肤上做的很好的真的没几个,在滚动条和菜单上我也是花费了2年的时间研究。这个库从2006年初开始动工,到现在已经3年半多了。一般标准控件的绘制我就不说了,基本查查msdn就可以了。下面我着重说下控件内部滚动条和菜单的换肤原理。
控件的内部滚动条并不是单独的控件,他是Windows窗体(或控件)的组成元素,滚动条换肤的难点就是如何禁止系统绘制,因为滚动条位于非客户区,当滚动条状态改变时系统会进行绘制,所以你无论怎么截获消息,都无法让系统不进行绘制。有些朋友可能用滚动条控件来代替控件内部的滚动条,但这是山寨的做法,弊端也是显而易见的。想让系统不进行绘制,那你必须全权接管滚动条信息的管理和绘制,Windows提供了有关滚动条的API,比如
GetScrollInfo,
SetScrollInfo等等,实际上Windows在内部维护的对用户开放的数据结构仅仅是SCROLLINFO这个结构体而已。那么,我们可不可以自己维护这个ScrollInfo,让用户或系统调用的滚动条相关操作都被我们所控制,我们来响应。答案是肯定的。这又回到之前我们说的原理Hook, 这次需要Hook的是API,
将滚动条的操作转入我们的处理过程,处理后返回给系统。这样就达到了我们的目的。至于API HOOK 方法很多,大家在网上一搜就可以找到一堆。
关于菜单的换肤,有些朋友可能已经知道菜单其实就是一个窗口类名为”#32768”, 所以你可以通过窗口类名来进行Hook了。主要的问题是你怎么才能获取到菜单的句柄呢?对于菜单消息,msdn上只有一个就是MN_GETHMENU, 不错,就是他。给菜单窗口发送这个消息就可以获取菜单句柄。这下好办了,有了菜单句柄,你可以随便操作了, 就这么简单。
还有一个比较难处理的就是窗口标题栏了,因为标题栏在元素改变时,系统也是会进行自绘的。很多换肤库(包括Skin++)的做法就是避开标题栏,把标题栏风格WS_CAPTION去掉,自己再留出一块非客户区再画标题栏,这种方法看似完美,其实弊端很多,对于窗体来说 WS_CAPTION是一个很重要的风格。去掉WS_CAPTION系统菜单的很多项目就失效了。另外还有其他方便的弊端,比如在Vista系统下就可能出现系统绘制边框等等。所以作为换肤库,应该尽量避免修改程序的默认属性和风格。换肤库好比人的衣服,不能说衣服不合适,就要把骨头拆了。如何解决这个问题呢?其实有很多方法,比如设置剪裁区,锁定绘制等等。
总结下来,换肤库真正说技术,并没有多么高深,原理道理很简单。经过skinsharp开发,小生收获最多的就是对Windows消息机制的理解,对Windows API的运用等等。当然在牛人面前,小生仍然是个菜鸟。毕竟花了很多心血,所以收费也是有需要的,小生不是大款,说白了就一个it民工。也需要生活,也需要钱。马云说过”免费是世界上最昂贵的东西。所以尽量不要免费。等你有了钱以后再考虑免费。” 虽然不能全部认可,但是还是有道理。
小生也乐意和有这方面兴趣的朋友一起讨论。
---------------------------------------------------------------------------------------------------
1.2、 界面库概况
1.2.1、界面库的由来
传统UI开发: 我们这里将不使用任何界面库的客户端界面的开发称之为传统界面开发。
软件界面在整个软件中与客户交流最频繁的一部分,只要用户操作软件,就必然与软件的界面打交道。而一款软件界面实现的是否完美,直接体现了该款软件在用户心目中的整体形象。在功能等价的几款软件中,如果一款软件的界面设计粗劣,性能低下,它在市场竞争中的地位可想而知。
目前,只要有点质量意识的软件开发商无不把软件界面提高到一个相当重视的程度。然而,伴随程序员一起天天工作的开发工具却不能很好地满足各种各样用户对界面设计与实现的需要。几年前,一个软件界面的设计与实现需要花费很大的人力。
现在,我们演示一下传统界面开发的过程。
我们从上面这个演示来看,可以总结出传统UI开发存在以下几个方面的不足:
一、软件开发工具自带的控件,它的UI的表现能力是比较有限的,难以满足软件对UI的需要,这种情况下我们需要对控件进行扩展类的编写,编写这样的扩展类本身具有一定的难度。需要UI工程师具备比较强的Windows底层编程基础和GDI的编程基础。我们这里只是对按钮进行了美化,也感觉挺麻烦的。而按钮的换肤,在整个换肤相对其他控件要容易的多,可以想象编写这么一整套的控件的工作量是相当大的。
另外,这样的扩展类多了以后呢,工程的代码量就日渐庞大,给软件后期的维护增加了困难。
二、控件换肤不彻底,如果我们想要对从Windows系统中弹出的打开文件对话框中的按钮进行换肤的话,那么就无法使用这种扩展类的方式做到,因为打开文件对话框的代码我们没有,也不在我们软件控制的范围内,是操作系统共享的窗体资源。
三、某些标准控件的自绘处理具有比较大的难度。比如滚动条的处理就是相当复杂的,
滚动条属于窗口的非客户区,大家很容易想到对WM_NCPAINT消息进行处理,但是在设置滚动条的滚动位置或动态调整滚动条大小时,滚动条又呈现 Windows默认的样式了。原来我们在设置滚动条位置时,系统调用了SetScrollPos 这个API函数,而这个API函数执行的时候就会调用Windows默认的内部绘制函数。这种情况下就难于处理了。所以某些标准控件的自绘处理具有很大的难度。
四、界面所用的图片、字体等资源放在工程的资源中。我们看刚才我们做的演示程序,我们将关闭按钮的图标放在了资源中,这些资源与代码间有着一一对应的关系,他们之间有很强的依赖关系,如果代码修改也相应地促使资源文件进行调整,资源的修改也会导致程序进行重新编译。
五、界面维护比较困难。如果想要另外做一套界面风格,很难做到不修改原有的界面代码。在这个演示程序中,我们需要再导入一个图标文件到资源中,而且我们的程序也需要修改。这样我们修改了原有的界面代码。这样对于客户端界面需求的变化就做不到很灵活地应对。
六、UI开发不能独立地同步进行。由于美术部分与界面程序部分依赖性强的特点,导致美术与程序必须有顺序地进行。之前的做法是,在确定产品的界面需求后由美工绘制高保真效果图,那么,确定后再开始程序开发,而高保真效果图一旦确定后而不能再做稍大的修改,因为这样的修改会直接影响到程序开发人员已做的工作。但有时很难在一开始就能把需求及效果完全确定,那么,这样就会导致界面编程工作在一定程度上的浪费。
基于以上的种种情况,国内外UI工程师都在思考如何很好地解决这些问题。
于是就出现了很类型的UI开发工具,又称界面库。
1.2.2、目前主流的界面库类型
目前主流的界面库有2大类型:
一、 扩展类界面库
就是将标准的类进行面向对象层面上扩展,并组织成一个动态库或静态库的形式向用户提供,用户调用导出类中的方法与属性进行界面的编程。
例如,我们在上面的演示程序中,将CButton的扩展类CButtonST类独立封装在一个单独动态库工程中,并将该类导出。我们将拥有了一个最简单意义上的按钮类界面库,当然我们可以添加其他的标准控件的扩展类进来,便形成了完整意义上的界面库。
这种类型界面库的代表:BCGControlBar
该类型界面库的优点: 与传统的UI开发相比,它提供了一整套的界面控件的实现,功能比较全面,有了这样丰富的类库后,用户不再需要自己写各种类型控件的扩展类了,同时这些控件被封装在独立的动态库工程中,界面控件的维护与升级相对要容易很多。
该类型界面库的缺点:
1、 随着界面库中的控件功能与样式不断增多,库文件也将变得越来越大,有时候用户只需要一个控件的美化,却要带上一个2M多的DLL文件。
2、 不能对操作系统标准的窗口进行美化。这种界面库要对某一个控件进行换肤,必须在代码中指定相应的类。而操作系统的标准窗口却没有机会让用户设置。
3、 与业务逻辑不能做到很好的分离。扩展类在软件工程中到处使用,与整个软件的耦合度非常高。
4、 对于已经开发完的软件不能很方便地进行界面的改造。这种类型的软件比较适合于软件正在开发阶段,最好一开始就使用该开发界面。因为扩展类的使用要求用户的类必须派生自他们。所以对于接近项目尾声的软件界面如果要采用这种类型的界面库,相当于界面部分重新开发,代价很大。
二、 外挂式界面库
扩展类界面出现的最早,属于第一代界面库。国内外UI工程师在使用第一代界面库时发现了其诸多缺点,并想进一步提升界面库的易用性,并真正做到界面与逻辑的彻底分离。通过不断改进与优化,逐步实现了外挂式界面库。这就是第二代界面库。
外挂式界面库,是通过遍历窗体中所有的控件,并截获所有控件的创建事件,实现自动对界面换肤。这种类型的界面库采用了HOOK与Subclass技术,技术上比较先进,效果上比较彻底并接近完美。
这种界面库的代表:Skin++
外挂式界面库的优点:
1、 换肤彻底。通过Hook可以获得所有控件创建的事件,所以连Windows系统的标准对话框也可以进行换肤。
2、 自动换肤。界面库可以在程序运行时可以知道控件创建的时机,并根据条件自行进行子类化,并响应窗口的绘图消息,这个过程并不需要用户在代码中进行控制。
3、 提供皮肤设计工具,实现界面资源与程序工程代码的脱离。一般第二代的界面库提供的皮肤设计工具,将程序中所有控件需要用到的图片、字体、文本等集中管理,并生成单独的文件。该文件通常称之为皮肤文件或主题文件。这样修改图片资源就不会对程序的工程代码产生影响。
4、 可以实现用户已有界面代码的最小程度的修改。使用该界面库,由于用户不需要进行所有控件的类的替换,所以用户修改代码的量是几乎没有。保留用户的代码不作修改,是这种界面库最大的美德。
5、 适合软件的各种开发时期使用。由于它与程序相对独立与自动,可以在软件开发时和开发完以后进行使用,无论哪个阶段使用都不会对用户的代码的产生影响。
6、 可以使得UI设计师与UI工程师可以同步地工作。美术设计师可以专注于图片的设计、切割和皮肤文件的制作,而不需要和UI工程师做太多的交互。
7、 界面维护比较容易。界面控件效果的维护与升级与用户程序代码之间没有关系,界面控件的升级与维护只与皮肤设计工具有关。所以界面维护相对独立并容易进行。
8、 可以优化整个软件工程代码架构。采用了这种界面库的程序工程的代码架构将大大优化。不再像之前的界面开发中遇到的代码之间的关系:剪不断理还乱。
它的缺点:
1、 自定义控件支持不是很有力。目前的外挂式界面库在自定义控件支持是个薄弱的环节。随着其他的功能的稳定,这个部分的功能也将跟上来。这是一个时间问题,不是一个原则问题。
2、 自动换肤,对于某些不想换肤控件,如用户自己绘制的控件不需要换肤时,需要用户在代码中手动停止该控件的自动换肤的功能。这种情况下,感觉稍有不便