贴个文章给你:
软件SKIN技术探秘
大家都知道,用户界面是一个软件的面子,界面好不好往往决定了一个软件的成功与否。我知道曾经有一
个程序高手,作了一个非常实用的软件,但基本上没有什么界面,只是一个简单的基于对话框的应用程序。
结果软件上传到Download.com和HOTFiles.com时,什么好评价也没有得到,Tucows.com甚至不肯收录该软件。
一位用户email说,"A good interface will lead you to a wonder" (一个好的用户界面会给你带来奇迹)。
于是他下决心改进用户界面,花钱请美术高手设计了一个很酷的界面,再投放到那些下载网站上,结果在
HotFiles上得了4个星,Tucows上得了4头牛。
好了,下面就言归正传,我要说的是当你请人设计了一个很酷的界面图片后,怎么把它集成到你的程序
中去?相信很多人用过OICQ,它采用了一种称作“skin(皮肤)”的技术,
界面比较华丽。实际上到它的程序目录里看一看,就会发现它的界面用到的图片都放在skins目录下。
下面我就分析一下这些图片是怎么贴到程序窗口中去的。当然这里的分析并非针对oicq,但我想原理大同小
异,不同的只是实现细节。限于小生才疏学浅,如果本文有何不妥,欢迎指正。
首先,制作一张图片,它用来做你的程序正常显示时的skin。我们姑且称它为main.bmp。
因此,你想象中你的程序界面是什么样子,那么图片就应是什么样子。如果你的程序需要按钮、标签等
控件,在这幅图中应该把它们画出来。下图是一个MP3播放程序的界面,图中画出了所打算实现的各种控件。
我想不用我一一解释了吧。
下面还要画两张图,称为over.bmp和selected.bmp。前者用于指出鼠标移动到某个控件上面时,控件的
外观(appearance)应该是什么样子;相应地,后者用于指出鼠标选中某个控件时,控件的外观应该是什么
样子。这两张图中,控件的布局(指控件的坐标、尺寸)安排应该与main.bmp相同,只是在控件的外观上有
些变化,以造成控件的动态效果
最后一张图片是mask.bmp,它用来创建一个区域(region)对象(利用api CreateRectRgn),基于此区
域可以实现不规则程序窗口(利用api SetWindowRgn)。生成该图片的办法是main.bmp中深色的部分全涂位黑
色,其余部分为白色,黑色部分其实就是你的程序窗口最后的轮廓(另外可以在程序中生成mask.bmp,办法
是把main.bmp拷贝成一个单色位图)。利用这种方法,你的程序可以是任意不规则窗口,它只局限于你的想
象力。(这里就不贴出来了,否则有赚稿费之嫌)
自然地,你会想到怎么在程序中把各种相关控件的信息从图片中提取出来呢?在这里我们的办法是预先
提取出各种信息并存入一个名为skin.ini的文件中,程序中读取该文件即可。提取办法很不好意思是手工提
取,比如需要某个按钮在窗口中的坐标信息,就在一个图形编辑器里确定它在图片中的坐标,然后手工写入
skin.ini。这样虽然比较麻烦,一般来说还是可以忍受的。(当然可以制作一个工具来生成skin.ini,那位
朋友感兴趣可以一试。)这里给出基于上述图片的一个skin.ini:
[SCREEN]
Mask=Mask.bmp
Main=Main.jpg
Down=Selected.jpg
Over=Over.jpg
Disabled=Main.jpg
[BUTTONINFO]
1=BUTTON_EXIT,349,8,17,17,关闭窗口,FALSE
2=BUTTON_MINIMIZE,342,7,17,17,最小化,FALSE
3=BUTTON_SAVEEQ,312,184,39,34,,FALSE
4=BUTTON_LOADEQ,271,184,39,34,,FALSE
5=BUTTON_USEEQ,234,184,39,34,,TRUE
6=BUTTON_LAST,341,143,35,30,最后一首,FALSE
7=BUTTON_NEXT,304,143,35,30,Sonraki,FALSE
8=BUTTON_PREV,267,143,35,30,謓ceki,FALSE
9=BUTTON_STOP,342,105,37,30,停止播放,FALSE
10=BUTTON_PAUSE,305,105,37,30,暂停,FALSE
11=BUTTON_PLAY,268,105,37,30,播放,FALSE
12=BUTTON_FIRST,230,143,35,30,第一首,FALSE
13=BUTTON_EJECT,231,105,37,30,弹出,FALSE
14=BUTTON_MENU,12,8,17,17,菜单,FALSE
[PROGRESSINFO]
1=PROGRESS_POS,进度,12,66,371,28,V
2=PROGRESS_VOL,音量,16,94,103,37,V
[TEXTINFO]
1=TEXT_POS,Arial,TRUE,FALSE,-13,65535l,310,46,54,14,
2=TEXT_LEN,Arial,TRUE,FALSE,-13,65535l,260,46,54,14,
3=TEXT_HINT,Arial,FALSE,FALSE,-13,0l,19,46,200,14,
4=TEXT_SONG,Arial,FALSE,FALSE,-13,0l,19,25,352,14,
可以看出,该文件主要存储了所需要的图片的文件名,以及各个按钮的名称、坐标以及提示信息
(tool tip)等等,其实具体存储哪些信息可由程序员自己定义,比如你当然可以存储菜单的颜色
(oicq中就是这样存的)。
好了,原材料都准备好了,下面就看怎么加工了。
程序开始执行后,我们处理WM_INITDIALOG消息,根据mask.bmp生成一个区域,并基于此区域使窗口不
规则化。然后在处理WM_PAINT消息时把main.bmp贴到窗口中,这时我们的窗口实际上已经穿上了一层漂亮
的skin。但它还不能响应用户事件。要想具备响应能力,下面有两种方案:
第一种:这种方案生成了一系列控件窗口。
根据skin.ini中存储的各控件的坐标,生成窗口(按钮、标签等等),设置其属性位自绘(OWNERDRAW),
并将它们子类化(SUBCLASS),使之响应WM_DRAWITEM(某些窗口如按钮、菜单等)或者WM_PAINT消息。在这
些消息的处理函数里面根据窗口的当前状态(鼠标未移动到窗口、鼠标移动到窗口中和鼠标选中)从相对应
的图片(main.bmp、move.bmp和select.bmp)中提取出相应的小图片(即控件对应的图片)。提取方法是根
据skin.ini保存的窗口坐标,用BitBlt函数从源图贴到控件窗口中。还要响应鼠标事件,在响应函数里面除
了上述的外表绘制工作外,还应加入实际应用有关的处理。
第二种:这种方案并不生成任何控件窗口。
在程序的主窗口的窗口函数里响应鼠标事件,当鼠标移动到某个“控件”(实际上是控件所在的矩形区
域,区域的坐标可以从skin.ini里得到)里面时,从move.bmp中提取出对应“控件”的小图片,在“控件”
所在位置加以显示。提取办法与第一中方案相同。同样地,当鼠标选中某个“控件”时,则从select.bmp中
提取出对应“控件”的小图片,在“控件”所在位置加以显示。
好了,到现在我们对skin技术已经有了一定的了解。可以看出,其实现原理实际上并不困难,我们完全
可以做到。当然,有些skin的实现与这里所说的看起来是不相同的,它们既没有图片,也没有skin.ini,其
实它们只不过把图片放到程序的资源中去了,控件的坐标信息放到程序中去了而已,归根结底原理是大同小
异的。本文偏重原理分析,如果有机会我会做一个例子来加以说明。