接着上次的教程继续。上次介绍了ButtonEx控件的设计,这次介绍ButtonEx的具体实现。这里我不打算说C语言的语法和简单的MiniGUI API调用,我就只说一些需要注意的问题。因为我认为这些才是大家真正需要了解的。
不管你是自己完全重新开始写控件类,还是继承现有的控件类。自己的控件类被外部程序使用前就必需要注册(通常是应用程序初始化的时候)。我目前还没深入看MiniGUI内部创建控件的过程。但是只有注册了的控件类,才能被CreateWindow()创建(自己外部写的控件只能通过CreateWindow来创建,能使用对话框模板的控件数据来创建):
BOOL RegisterButtonExControl (void)
{
WNDCLASS BEXClass;
… …
// 填写控件类信息
BEXClass.spClassName = CTRL_BUTTONEX;
BEXClass.dwStyle = WS_NONE;
BEXClass.dwExStyle = WS_EX_NONE;
BEXClass.iBkColor = PIXEL_lightwhite;
BEXClass.hCursor = GetSystemCursor (IDC_HAND_POINT);
BEXClass.WinProc = ButtonExProc;
BEXClass.dwAddData = (DWORD)pCData;
… …
// 注册ButtonEx 类
return RegisterWindowClass (&BEXClass);
}
还记得在教程2里提到的获取控件类信息的函数GetWindowClassInfo()吗。那里获取的信息正是你这里注册的信息。注意这里要把WNDCLASS的所有变量都填满,不然当应用程序程序使用你的控件时程序会出错的。填满好信息后,调用RegisterWindowClass()注册控件类。在控件使用前要进行注册,当控件使用完后(通常是应用程序退出前),就要注销之前注册的控件:
BOOL UnregisterButtonExControl (void)
{
… …
// 卸载ButtonEx 类
return UnregisterWindowClass (CTRL_BUTTONEX);
}
调用UnregisterWindowClass()来注销控件类,传入的参数是之类注册的控件类名字(字符串)。
在教程2中介绍了实例变量的保存方法(复习下:使用每个窗口的dwAdditionalData2保存控件数据变量指针 ^_^),这里说下类变量如何保存。其实这个MS根本就不用说,因为实例变量是每个控件实例都有一份的,所有要特殊处理下。这个所有的控件都共用一份的,直接弄个全局变量就行了。建议使用static变量,这样能让类变量只具有文件范围,防止外部随便访问(谁让C语言没有类呢)。
虽然我把这个叫做完全自己写控件类,但是实际上它还是继承了一个父类的,就是所有的控件的父类(相当于CObject)。这个其实就是一个函数的调用,在之前也说过了的。这个函数就是:
DefaultControlProc (HWND, int, WPARAM, LPARAM);
在控件的过程处理函数结尾,调用父类的这个处理函数,让其处理一些本控件不处理的系统消息。
其实光是完全自己重新写控件需要注意的问题,上面就已经说完了。但是既然我的这个例子是Button功能的控件,就说下和这个控件相关功能实现注意的一些问题吧。先说功能相关的实现,界面图形表现的实现放在下面说。
1:在对话框中正确得到键盘输入
这里涉及到一个关键的消息的返回值:
case MSG_GETDLGCODE:
{
return DLGC_PUSHBUTTON;
}
这个消息API手册解释的非常简单,仅仅只是把这个消息的名字复制了一下 -_-|| 。但是如果这个消息处理不正确,你的控件就无法在对话框(Dialog)中正确的得到键盘输入。不过这里的ButtonEx控件和这个没多大的关系,但是还是说明一下吧。这个消息的含义是获取对话框码。因为你如果创建的窗口是对话框的话,它会比一般的窗口多处理一些消息,这些消息包括某些按键的响应。因为对话框有些功能:tab、left、right、up、down遍历WS_TAPSTOP风格控件;esc发送IDCANCEL;BS_DEFPUSHBUTTON响应enter按键。就是说如果你不不响应这个消息(默认返回值为0),你的控件在对话框下就无法得到收到tab、left、right、up、down、enter、esc这些按键的消息。但是某些控件是需要这些值的,例如listview、listbox、eidt等。这时就要给予MSG_GETDLGCODE消息正确的返回值,告诉对话框你的控件要处理这些按键消息。这样对话框处理函数才会“放过”这些按键的消息,发送到你的控件处理。这些值包括如下(可以用或来同时获得):
DLGC_WANTARROWS 要处理上下左右按键
DLGC_WANTTAB 要处理tab键
DLGC_WANTALLKEYS 要处理所有的按键
DLGC_WANTENTER 要处理enter键
2:使用控件风格
你可以给你的控件设计多种风格,然后通过创建控件时,指定不同的风格获得不同的功能。我不太清楚从MiniGUI多少版本起,开始有2个变量来保存风格了:一个普通的风格,一个扩展风格。控件风格用的是普通的风格变量,这是一个32bit DWORD类型,每一位代表一个风格,然后通过位运算得到当前风格的设置情况。高16bit被MiniGUI用来保存通用窗口的风格了(WS_TABSTOP、WS_VISABLE等),留给控件用的是低16bit,这点要清楚,不然如果你自己控件的风格定义成高16bit的话,就会出现逻辑错误了。然后通过GetWindowStyle()来获取当前普通风格。ButtonEx的风格定义如下:
#define BEXS_TYPEMASK 0x0000FFFFL
#define BEXS_IMAGE 0x00000001L
#define BEXS_BKIMAGE 0x00000002L
#define BEXS_DRAW 0x00000004L
3:发送通知码
给父窗口发送通知码的API是这个:
NotifyParent (HWND parent, int id, int code);
第一个、和最后一个参数没什么好说的,第二个参数是当前控件的ID号,这个通过GetDlgCtrlID ()函数获取。这里是虽然是简单的API调用,可以编程指南里没写,网上又搜不到,我是看源代码才知道,要这么用的 -_-|| 。
4:判断鼠标点击
MiniGUI原来的Button是只要在控件客户区弹起鼠标左键才会发送点击通知码的。不仅仅MiniGUI的Button这样,MFC、.net、java等GUI的Button都是这样(不知道谁先这么规定的-_-||)。要实现这个功能,要在鼠标左键按下时捕获鼠标,然后弹起鼠标后再释放,这样才能判断弹起的鼠标左键的坐标。这里提示下,本来MSG_LBUTTONUP得到的鼠标坐标是client坐标的,但是一捕获鼠标后就变成srceen坐标了,注意坐标转换。
这里主要说的MiniGUI中一些图像API的使用,以及需要注意的一些问题。这里先说下颜色的一些基本知识吧(知道了的可以忽略)。图片由像素组成,例如一张90x40的bmp(位图),就由90x40个像素组成。每个像素又由如干位(bit)组成,例如8bit、16bit、24bit,这个就是我们说的色深。计算机里的颜色一般用用得最普遍的RGB(红、绿、蓝)三色组成。现在用一个数值分别表示这三种颜色的亮度,例如范围为255,值越小就越黑,值越大就越亮,这样由3个0~255的数值混合可以组合成很多种颜色(这个叫做像素值,pixel)。这里就可以看到刚刚说范围是0~255,这样一种颜色就需要8bit(2^8),3种颜色就是24bit(3x8),这就是我们平常说的24色深。这样的颜色组合能够达到2^24=16777216种,已经达到人眼的分辨率极限。但是一般在嵌入式中,一般显示屏幕达不到这么高的颜色显示,现在一般带GUI的嵌入式产品的主流色深是16bit,就说分给RGB每个分量的位数不足8bit,这样有些颜色就会显示不出来。所以这里要特别注意,在ButtonEx控件中使用的图片,如果是24bit的话是会有可能失真的。不过不用太担心,因为16bit的能够显示大多数常用的颜色的,所以只要使用的图片颜色渐变效果不是特别夸张都不会有太大的失真(一般界面那些花哨的效果都是由颜色渐变弄出来的)。
不过大家在用的桌面操作系统的色深是32bit,那除了刚刚说的24bit用于RGB之外,还有8bit是用来干什么了咧?32bit的色深还有8bit用来表示alpha通道,这个通道用来表示颜色的透明度(我至今不明白为什么叫alpha -_-||)。在8bit的情况下,数值越小透明度越高,0表示完全透明,255表示完全不透明。大家在网上看到的png格式的图片就是带alpha通道的。
这里再说说图片格式。bmp最高是24bit色深,bmp不带alpha通道信息,因此bmp本身不带透明信息。并且bmp是没有压缩过的图片格式,所以一般bmp格式的图片都比较大(这个很好理解:例如一张1024x768x24bit的bmp大小就是:1024x768x24/8x2^20=2.25MB)。MiniGUI无需外部库就支持bmp格式图片(因为直接读取它的数据就是图片信息,要不然怎么叫位图咧)。jpg最高也是24bit的,同样也不带alpha通道信息,但是这种图片格式是经过压缩算法的,所以同样大小的图片,jpg格式的能比bmp格式的小得多。MiniGUI需要外部jpeg库才能支持这样格式的图片。png图片,最高色深32bit,这个是带alpha通道信息的,而且也是经过压缩算法的,体检很小,所以网上那些透明的很好看的图片一般都是png的(又可以透明,体积又小)。MiniGUI同样需要外部png库才能支持这种格式。gif么,我不太清楚,它一般是用于动画的,不过也可以用来表示静态图片(一帧动画不就是静态的么-_-||)。gif也可以带alpha通道,MiniGUI无需外部库就可以支持这种格式。我这里只是说说一般嵌入式产品用到的图片格式,还有其它的一些专业的格式,我就不说了,而且我本人也不是太了解。
上面罗嗦这么多,其实主要是ButtonEx要使用到以上说到的那些格式的图片,如果你连图片的特性都不清楚的话,是不可能很好的应用它们的。好,现在就说说如何应用这些图片来使我们的ButtonEx变得好看起来。
1:背景图片
这里说的背景图片不光光单指BEXS_BKIMAGE的背景,也包括BEXS_IMAGE,BEXS_IMAGE其实所有的都通过图片来表现了(这里没BEXS_DRAW什么事了)。背景推荐用bmp。因为一般按钮不会太大,所以图片不会太大,jpg也省不了多少空间。但是jpg的压缩是失真的,能不用就尽量不用啦。bmp虽然不带alpha通道信息,但是一般作为button的背景不太需要这个,而且就算需要,也有办法代替。如果用png的话,就我的测试发现,稍微大点的32bit的png用在16bit上就能看出失真了(这个不能算明显,但肉眼也能看得出,大家看我教程3中有一张中的第1个按钮和第3个按钮就知道了),所以还是用bmp的效果好。这里如果要做到一些类似圆角的按钮效果的话,其实用不带alpha通道的bmp也能做到,而且在16bit色深下bmp效果还好些。
如果窗口没有背景图片。这种情况最好办,窗口如果不带背景图片,那它就是单一的一种RGB颜色。可以通过种种方法得到窗口的背景颜色。然后把图片的背景弄成这种颜色,哈哈,这样用户一看就感觉按钮时圆角的啦。但是建议这样不规则形状的图片,不要弄得太夸张,因为就2.0.4版本的MiniGUI还是不支持不规则窗口的,所以响应鼠标还是以矩形来算的,弄得太夸张的话,很容易就露馅了 -_-|| 。经我测试这种方法既不需要开启透明颜色,效果也是最好的。如果窗口窗口有背景图片,就稍微麻烦一点了。因为如果有背景图片的话,用上面那种方法就不行了,因为背景图片可不是单一的一种颜色了。这个时候如果用bmp的话,就需要开启透明颜色。这里就说之前设计说那个开启背景透明颜色的功能了。这里主要用到MiniGUI BITMAP结构里的bmType、bmColorkey这2个变量。这个看API手册和飞漫的mde里那个高级的绘图例子就清楚了,你看我的源代码也可以清楚 ^_^ 。不过这里提醒一下,用这个方法,MiniGUI在用图片绘图图像的时候是只能透明掉你指定的那一种颜色的;但是目前一些的图像处理软件,如PS,是会很“好心”的帮你的图片的图层和背景过渡的部分加上适当的过渡颜色的。所以出来的效果就是有锯齿,因为那些过渡的颜色没有被透明掉。目前的处理办法是:1:采用“抗锯齿”的算法。其实只要把那些过渡的颜色在内存中换成透明的颜色就行了,它们之间的RGB值差别不是很大的。不过我现在还没怎么研究MiniGUI中RGB值到pixel值之间的转化,所以这个函数也是没弄。2:处理图片。就是手动把那些过渡值换成背景颜色(这个把图片放大就看出来了)。
2:图标图片
主要是BEXS_BKIMAGE和BEXS_DRAW风格用到这个了。这个推荐用png图片。原因其实上面已经说了,因为按钮的背景是图片(BEXS_DRAW的虽然不是图片,但是也不是单一的颜色),不是单一的颜色,用bmp透明效果不怎么好。一般图标比较小(一般是16x16、32x32),所以即使是32bit的png用到16bit上也不怎么失真。带alpha透明通道后效果很好。这里说一下,MiniGUI载入图片,不管原来图片存储的是什么格式的,载入后在内存中都是BITMAP变量(MiniGUI通过图片的后缀自行判断)。正常状态的图标没什么好说的,直接载入,然后用绘图函数画就是了。
现在说说BEXUI_DISABLE状态的特效。这个特效是使图标呈现一定的透明效果。注意这里说的透明不是说png图片自己带alpha信息。这里再说说,png原来带的alpha通道叫做逐点alpha信息。是每个像素都有的(32bit中的8bit),能够精确到每个像素点。我这里说的是不管它原来逐点的alpha信息是什么,整个图片以一个统一的alpha值进行透明(这个又叫做alpha混合)。不过这里提醒下,BITMAP结构里有一个bmAlphaPixelFormat的变量,叫做私有像素格式。所谓的私有像素格式就是和MiniGUI使用的图像格式不一样的格式。例如我使用的miniugi是1280x1024-16bpp,就是16bit的色深,但是我载入的png是32bit的,它们的颜色格式肯定不一样。所以这个私有像素格式就保存了图片自己的图像格式。经过我实验,凡是有私有像素格式的BITMAP变量,均无法使用bmAlpha、bmColorKey变量。就是说如果我们载入的是32bit带alpha的png就无法使用BITMAP的alpha变量来实现alpha混合效果。但是咋有“曲线救国”办法:MiniGUI的内存DC结构也有alpha变量可以设置。我们可以把png先绘制到一个内存DC里,然后设置内存DC的alpha变量,就可以显示BEXUI_DISALBE状态的alpha混合效果了。这里的内存DC可以看成是一张画布,我们把png图片完整的信息绘制到画布上,然后把这个画布弄成“半透明”,最后再把这张画布贴到控件的客户区上。效果见教程3中的Disable图片,很cool吧 ^_^ 。这也就是之前设计图标变量时,为什么还会有一个HDC变量。不过开启这个效果是要额外占用资源的,因为每个控件都额外需要创建一个HDC,所以资源比较紧张的情况下还是关掉吧(关掉的话ButtonEx不会创建这个alpha混合用的HDC的)。
至于BEXS_DRAW风格的那个颜色渐变算法是照搬飞漫2.0.4中fashion风格下Button的绘制算法的。我只是稍微改了一点点而已,我自己也没啥好说的,因为基本算是照抄 -_-|| 。不过用这个风格可以省去自己P图了,直接去网上找一些png图标就可以了。
上面说了这么多,感觉好像也挺混乱的。哎~~因为本来我对这些东西就接触不多,都是临时自己琢磨的。说得不对的请大家指正。可能光看我写的文字还是没啥子感觉,不过这个算是个思路吧。具体还是看代码吧,我的代码里为什么用这个API,为什么这样做,都有详细注释的,相信大家都能看的懂。
这里顺带介绍下附带的代码。这个是用source insight建立的工程,共有4个自定义控件:ButtonEx、SLEditEx、RollShow、ProgressbarEx。分别是按钮自定义控件(这次介绍的就是它了),单行编辑框自定义控件,滚动显示控件,进度条控件。这是我到目前为止用MiniGUI写的自定义控件,里面都有详细的功能、说明、注释,大家可以参考、参考。后面几次的教程也是以这个工程为例子的。
这次介绍了如何自己完全从头开始写MiniGUI的控件,并且也“趁机”介绍了下MiniGUI中一些图片,绘图处理的方法。下次我将介绍如果通过继承MiniGUI原有的控件,来扩展原有控件的功能。
参考资料:飞漫MiniGUI编程指南2.0.4
原始出处:飞漫wiki(原作者就是我)
代码下载:下载地址