3 MzMenu_GUI源代码分析 3.1Menu_Resource.c菜单资源定义 Mz_MenuGUI的菜单系统当中,在Menu_Resource.c文件里完成了所有的菜单字符资源以及菜单项的定义,主要定义有三类如下,可以从源码中看出:
//==================== // 文件名: Menu_Resource.c // 作 者: Xinqiang Zhang(email: Xinqiang@Mzdesign.com.cn) // www.Mzdesign.com.cn // 日 期: 2007/03/24 // 描 述: 菜单应用范例程序--UI显示资源定义文件 // 有关汉字字库的资源请参考铭正同创网站上有关LCD显示中文的文章,或 // 直接参考铭正同创(Mzdesign)提供的LCD通用版基本驱动程序 // // 参 考: // 版 本: // 2007/03/24 First version Mz Design // 2007/07/26 V1.01 Mz Design // //==================== //定义单条菜单项内容,格式有两种,一为支持汉字库LCD的纯汉字菜单项,另一是西文字符与自定义 //汉字库的混合菜单项的如下: //一:直接用汉字的字串即可,不同的编译器可能在汉字的GB码数据类型上有所不一样 //二:菜单项字符数,第一个字符在字库中的序号,第二个字符,.... //注:在第二种情况下,为区分自定义汉字与ASCII码,特定将自定义汉字库中的汉字编码前加128 // 作为标识 code unsigned char Menu_String01[]="基本功能演示"; code unsigned char Menu_String02[]="位图显示功能演示"; code unsigned char Menu_String03[]="数字编辑输入演示"; code unsigned char Menu_String04[]="滚动消息框演示"; code unsigned char Menu_String05[]="保留"; //定义某一组菜单的配置,格式如下: //{该组菜单的菜单项数目,该组菜单中汉字所选用的字符类型,该组菜单中ASCII码所选用的类型, //该组菜单中每条菜单项所占用的Y轴大小,该组菜单中菜单项显示的X轴偏移位} code unsigned char Menu_List01_Config[]={5,0,0,14,10}; //定义一组菜单的菜单项,格式如下: //{该组菜单所对应的配置,第一条菜单项,第二条菜单项......} //注:菜单组列表中菜单项的数目要与相应的配置里一致 code unsigned char *Menu_List01[]=// {(unsigned char *)Menu_List01_Config,(unsigned char *)Menu_String01, (unsigned char *)Menu_String02,(unsigned char *)Menu_String03, (unsigned char *)Menu_String04,(unsigned char *)Menu_String05}; |
代码前面的地方,定义了单条菜单项的内容,代码的注释里已有格式的简单说明了,用户需要参考铭正同创的通用LCD驱动程序当中关于字库的介绍,因为这里的定义是与驱动函数中字库的定义相关的。 在本例中,全部使用了中文菜单。 而菜单项要定义多少个就由用户自行选择了,在上面的代码当中,共定义了5个菜单项,跟显示效果里的一样,分别是: “基本功能演示”、“位图显示功能演示”、“数字编辑输入演示”、“滚动消息框演示”、“保留”。
接下来定义了一个数组,用于一组菜单的配置,也就是说可以在程序里面定义多组菜单,然后每一组菜单都可以拥有自己的特性配置。下面看看这个配置的数组里具体定义的含意。 Menu_List01_Config[]={5,0,0,14,10}其中定义了5个数据,第一个数据为声明该组菜单当中共有多少个菜单项;比如在前面的介绍中得知,本例的菜单共定义了五个菜单项,所以在这里将该数值设置为5;接下来的一个数据代表混合字符菜单项里的汉字字库序号(用户请参考MzLH06模块的驱动程序以及该模块手册),定义为0(12*12点阵的汉字);接着便是ASCII码西文字库的序号,为0。 而其中的数据14表示该组菜单当中每一个菜单项所占用的Y轴的点数,通常该数要大于或等于该组菜单当中的字符里Y轴方面最大的值;这里可知前面定义的菜单项里面采用的是12*12点阵的字符,所以该值定义为了14。最后的一个数据表示菜单项显示时,对X轴原点的固定偏移数;可以从下图中看出在数组中定义的10的意义:
如上图所示意的,如果想要菜单项的字符从X轴为0的地方开始显示的话,只需要将菜单配置数组中的最后一个数设为0即可。
最后一个定义是对一组菜单的定义了,用户在菜单响应的控制程序里会使用到该数组,一组菜单定义对应一个菜单界面,如果有多级菜单的话,可以定义多组菜单,格式参考代码里的定义即可。这组数据当中,第一个量为该组菜单的配置,接下去的数据为该组菜单当中的每个菜单项的定义。
注意:可能不同的编译器对指针的编译会有所不同,包括常量的定义,这点请用户注意,要移植该代码时可能要作出适当的修改。
3.2Menu_GUI_Config.h菜单GUI配置头文件 菜单GUI的配置头文件里面定义了菜单的字符色等,下面是源码:
typedef unsigned int UINT; typedef unsigned char UCHAR; #define COLOR_1 0x0001 //黑色 #define COLOR_2 0x0000 //白色
#define MENU_BACK_COLOR COLOR_2 //定义菜单系统当中的背景色 #define MENU_FONT_COLOR COLOR_1 //定义菜单系统当中的文字色 #define MENU_SELE_COLOR COLOR_1//定义菜单系统当中被选择的菜单项背景色 #define MENU_SELF_COLOR COLOR_2//定义菜单系统当中被选择的菜单项文字色
#define Hz_Lib_II 1 //使用二级汉字库的定义,如果LCD的驱动中支持二级字库 //则使该定义有效,否则将其屏蔽 |
最前面用了两个typedef对两个数据类型进行了重定义;然后用define定义了两种颜色(该菜单GUI代码是可以适应彩色的LCD的,所以这里的定义为将来的扩展打基础),再跟着下来是重定义了几个常量,它们在代码中都有注释,这些颜色的重定义将会在Menu_GUI.c的代码当中使用。 最后的一个定义是针对于带字库的LCD模块而作的扩展定义。
3.3Menu_GUI.c菜单接口函数 先介绍两个跟配置有关的函数:
//==============// //函数:UCHAR GetMLNum(UCHAR ** Menu_List) //描述:获取菜单资源的菜单项个数函数 //参数:Menu_List菜单资源链表指针 //返回:菜单项个数 //注意:无 //============// UCHAR GetMLNum(UCHAR **Menu_List) { UCHAR uiTemp; uiTemp = (unsigned char)**Menu_List; return uiTemp-1; } //================// //函数:UCHAR GetMLiNum_Page(UCHAR ** Menu_List) //描述:获取菜单资源在一屏可以显示的菜单项个数函数 //参数:Menu_List菜单资源链表指针 //返回:菜单项个数 //注意:无 //===============// UCHAR GetMLiNum_Page(UCHAR** Menu_List) { UCHAR uiTemp; UCHAR *Menu_Config; Menu_Config = (UCHAR *)(Menu_List[0]); uiTemp = *(Menu_Config+3); uiTemp = (Dis_Y_MAX+1)/uiTemp; return uiTemp; } |
如注释所述,UCHAR GetMLNum(UCHAR ** Menu_List)函数是获取一组菜单资源里面的菜单项个数的,传递进该函数的参数就是上一小节当中所说的一组菜单的定义数组,返回的是该组菜单资源当中的菜单项个数;不过要注意的是跟前面介绍的定义有点不一样,在前面介绍时,如果菜单项个数为5个的话,在定义里面就直接将菜单资源配置数组里的第一个量置为5了,这里的返回值也是读取了该数据,但减去了1,因为菜单项的序号从0开始。 UCHAR GetMLiNum_Page(UCHAR** Menu_List)函数与LCD驱动程序当中的配置有关,它的功能是获取一屏LCD显示当中共能显示菜单项数量,代码中使用了Dis_Y_MAX(该定义在铭正同创提供的通用版LCD驱动程序中的LCD_Config.h当中,定义了LCD模块在Y轴方向的最大显示点数)的配置,并除以菜单项的Y轴点数,得到的是一屏LCD显示能容下的菜单项数。
在菜单显示的代码里面,定义了几个全局的变量,用于更新显示时使用,如下:
#include "LCD_Dis.h" #include "LCD_Config.h" #include "Menu_GUI_config.h"
UCHAR Y_WIDTH_MENU=16; UCHAR X_SPACE_FRONT=10;
UCHAR Dis_Menu_Num=0;
UCHAR Font_GB=0; UCHAR Font_String=0; UCHAR First_Index_old=0xff; UCHAR y_Index_old = 0xff; |
Y_WIDTH_MENU存放有当前菜单组的一个菜单项占用Y轴点数,与菜单组配置数组当中的定义是一样的意义;只不过可能有多个菜单组资源存在于用户的应用程序当中,而这个变量保存的是当前选择的,也就是当前显示在LCD当中的菜单组的。 X_SPACE_FRONT存放着当前菜单组在显示菜单项时,菜单项字符偏移X轴原点的点数。 Dis_Menu_Num则保存着当前的菜单组能在一屏LCD当中显示的个数。 Font_GB中为当前的菜单组当中选用的自定义汉字库序号,Font_String则是ASCII码西文字库的序号。
First_Index_old变量保存的是菜单当前显示在LCD屏上的首行菜单项的序号;由于菜单组可能有多个菜单项,而菜单项个数大于一屏能显示的菜单项个数时,首行的菜单项就有可能会在用户操作菜单时发生变化;此变量将在菜单的显示刷新当中使用。 y_Index_old则保存关当前菜单项当中,处于活动的那项序号,也就是选择了哪一项菜单。 First_Index_old和y_Inded_old变量初始化它们的值为0xff,而在代码中也利用了该数值(0xff)在下面的代码当中会有介绍。
接下来,有两个控制函数,功能差不多,只是有细微的区别,在用户编写菜单响应控制函数时有用:
//=============// //函数:void Redraw_Menu(UCHAR First_Index,UCHAR Menu_Index, UCHAR ** Menu_List) //描述:刷新整屏菜单显示函数 //参数:First_Index当面显示页的第一条菜单号 //Menu_Index当前处于选用的菜单项 //Menu_List菜单资源链表指针 //返回: //注意:无 //=============// void Redraw_Menu(UCHAR First_Index,UCHAR Menu_Index,UCHAR** Menu_List) { UCHAR *Menu_Config; First_Index_old=0xff; Menu_Config = (UCHAR *)(*Menu_List); Font_GB = *(Menu_Config+1); Font_String = *(Menu_Config+2); Y_WIDTH_MENU = *(Menu_Config+3); X_SPACE_FRONT = *(Menu_Config+4); Dis_Menu_Num = (Dis_Y_MAX+1)/Y_WIDTH_MENU; UpDate_Menu(First_Index,Menu_Index,Menu_List); } //===============// //函数:void Initial_Menu(UCHAR ** Menu_List) //描述:刷新整屏菜单显示函数 //参数:Menu_List菜单资源链表指针 //返回: //注意:无 //===============// void Initial_Menu(UCHAR** Menu_List) { UCHAR *Menu_Config; First_Index_old=0xff; y_Index_old = 0xff; Menu_Config = (UCHAR *)(*Menu_List); Font_GB = *(Menu_Config+1); Font_String = *(Menu_Config+2); Y_WIDTH_MENU = *(Menu_Config+3); X_SPACE_FRONT = *(Menu_Config+4); Dis_Menu_Num = (Dis_Y_MAX+1)/Y_WIDTH_MENU; UpDate_Menu(0,0,Menu_List); } |
Redraw_Menu和Initial_Menu函数的功能相似,可以从代码中看出来,在函数里面都要对前面介绍的全局变量进行设置,并调用UpData_Menu函数进行重绘菜单。 而在Initial_Menu函数当中,将First_Index_old和y_Index_old重置为0xff,则是要全部重新的绘制菜单,使当前显示屏中第一列菜单项为该菜单组中的第一项,而当前选择的菜单项初始化为该菜单组中的第一项;函数中传递进来的Menu_List参数为要选用的菜单资源链表(也就是二维数组)。 Redraw_Menu函数有三个参数,分别在注释中已有说明;在该函数里与Inialt_Menu的区别也就是对y_Index_old的初始化,其实也就是Redraw_Menu用于重绘菜单,但菜单组中显示在LCD屏上的第一项菜单项并不一定是菜单组中的首项,而是由Fist_Index指定;而当前处于选定状态的菜单项由参数Menu_Index指定。此函数一般使用在菜单响应后,用户程序进入了另外的显示界面的,当返回时重绘菜单使用。
菜单显示控制函数主要只有两个,无论用户定义了多少组菜单资源,都是这两个函数来完成显示。下面分别看一下它们的代码: UpDate_Menu菜单更新函数:
//=================// //函数:void UpDate_Menu(UCHAR First_Index,UCHAR Menu_Index, UCHAR ** Menu_List) //描述:刷新整屏菜单显示函数 //参数:First_Index当面显示页的第一条菜单号 //Menu_Index当前处于选用的菜单项 //Menu_List菜单资源链表指针 //返回:无 //注意:无 //=================// void UpDate_Menu(UCHAR First_Index,UCHAR Menu_Index,UCHAR** Menu_List) { UINT y_width,y_Index; UCHAR List_Num,i; List_Num = (UINT)**Menu_List; y_width = Y_WIDTH_MENU; y_Index = 0; while(First_Index>List_Num) First_Index -= List_Num; ① if(List_Num>Dis_Menu_Num) List_Num = Dis_Menu_Num; if(First_Index_old!=First_Index) ② { SetPaintMode(1,MENU_BACK_COLOR); ClrScreen(); //清屏 SetPaintMode(1,MENU_SELE_COLOR); if(y_Index_old==0xff) ③ { Rectangle(0,First_Index*y_width,Dis_X_MAX, First_Index*y_width+y_width-1,1); i=First_Index+1; ShowMenu_Item(y_Index,(UCHAR *)Menu_List[i++],MENU_SELF_COLOR); y_Index = y_Index+Y_WIDTH_MENU; for(;i<List_Num+1;i++) { ShowMenu_Item(y_Index,(UCHAR *)Menu_List, MENU_FONT_COLOR); y_Index = y_Index+Y_WIDTH_MENU; } } else ④ { y_Index_old = Menu_Index-First_Index; ⑤ Rectangle(0,y_Index_old*Y_WIDTH_MENU,Dis_X_MAX, y_Index_old*Y_WIDTH_MENU+Y_WIDTH_MENU-1,1); i=First_Index+1; while(List_Num) { if((i-1)==Menu_Index) ShowMenu_Item(y_Index,(UCHAR *)Menu_List, MENU_SELF_COLOR); ⑥ else ShowMenu_Item(y_Index,(UCHAR *)Menu_List, MENU_FONT_COLOR); ⑦ y_Index = y_Index+Y_WIDTH_MENU; i++; List_Num--; } } First_Index_old = First_Index; ⑧ } else ⑨ { y_Index = y_Index_old-First_Index_old; y_Index = y_Index*Y_WIDTH_MENU; SetPaintMode(1,MENU_BACK_COLOR); Rectangle(0,y_Index,Dis_X_MAX, y_Index+Y_WIDTH_MENU-1,1); ShowMenu_Item(y_Index,(UCHAR *)Menu_List[y_Index_old+1], MENU_FONT_COLOR); y_Index = Menu_Index-First_Index; y_Index = y_Index*Y_WIDTH_MENU; SetPaintMode(1,MENU_SELE_COLOR); Rectangle(0, y_Index,Dis_X_MAX, y_Index+Y_WIDTH_MENU-1,1); ShowMenu_Item(y_Index,(UCHAR *)Menu_List[Menu_Index+1], MENU_SELF_COLOR); } y_Index_old = Menu_Index; } |
菜单更新函数较为复杂,下在分段分析代码。 函数多次调用到的子函数ShowMenu_Item是菜单项绘制函数,将在接下去的代码中介绍,该函数有两个参数,一个是要绘制的菜单项,一个是菜单项的颜色。
①:判断传递进来的参数Fisrt_Index是否超出了选用的菜单组的菜单项个数,如果超出了则处理一下;接着对List_Num进行处理,该变量的意义为一屏LCD能显示菜单项的个数,如果当前选用的菜单组的菜单项个数大于一屏LCD能显示的菜单个数,则取后者的值;如果等于或小于,则取当前菜单组的菜单项个数。 ②:判断First_Index_old是否等于传递进来的参数First_Index,如果不相等则表示当前LCD屏显示当中的第一列菜单项发生较之前发生了变化,需要重绘整个菜单。 要重绘全部菜单时,首先要将屏幕全部清屏。 ③:此处判断y_Index_old是否为0xff,如果是0xff,则认定为初始化菜单时的重绘菜单,程序会将当前屏的LCD显示的第一列菜单项选择为菜单组中的第一项菜单项,而处于选择状态的菜单项为菜单组资源当中的第一项菜单项。 重绘菜单时,先绘制处于选择状态的反色条,与前面图样里屏幕上与背景的白色反色的黑色条一样,当然对于彩色的LCD是可以选择很多的不同颜色的。然后在同样的位置上绘制出该条处于选择状态的菜单项,也就是菜单组资源中的第一项菜单项了。随后绘制其它的不处于选择状态的菜单项。 ④:跟随着前面的判断,如果y_Index_old不为0xff的话,则是正常的重绘菜单,也就是通过Redraw_Menu函数调用进来的,或者是LCD屏上首行显示的菜单项发生了变化。 ⑤:这时,也是首先绘制处于选择状态的反色条,它的位置由Menu_Index-First_Index计算出来,Menu_Index是传递进来的参数,代表当前菜单当中处于选择状态的菜单项是菜单组资源当中的第几项,而First_Index则是当前菜单项当中处于LCD屏显示的首行菜单项是菜单组资源当中的第几项,两者相减便得出当前处于选择状态的菜单项应该在LCD屏当中的具体位置了;这里使用y_Index_old变量暂时保存一下位置数据。 然后,根据计算出来的当前处于选择状态的菜单项的位置绘制反色条,最后依次绘制一屏LCD可以显示的多个菜单项。 ⑥:当然在绘制菜单项时会判断是否为处于选择状态的,如是则选用配置的选择菜单项字符色来绘制。 ⑦:否则就使用正常的菜单项字符色绘制菜单项字符。 ⑧:这里将更新菜单前的LCD屏显示首行菜单项序号更新,以便在下次调用显示时使用。 ⑨:最后,如果不必要对全屏进行菜单刷新的话,就会进入该分支进行对菜单的刷新了,也就是y_Index_old和First_Index_old都没有被初始化为0xff,而且First_Index_old与传递进来的参数First_Index是相同的,也即说明LCD屏上的首行菜单项没有发生变化。 然后,计算出当前LCD屏上显示的处于选择状态的菜单项位置,将该位置的显示清零,重绘上该位置的菜单项字符;再计算刷新后处于选择状态的菜单项的位置,在该位置绘制反色条,最后再绘制处于选择状态的菜单项。
ShowMenu_Item菜单项绘制函数前面介绍的LCD驱动程序当中的字库定义以及菜单项的定义有很紧密的关系,下面是它的代码:
//====================// //函数:UCHAR ShowMenu_Item(UCHAR y,UINT* Menu_String,UCHAR Font_Color) //描述:显示菜单项子函数 //参数:space_front显示缩进值 //y Y轴坐标 //Menu_String菜单项链表的首地址指针 //返回:显示溢出情况 0:溢出 1:无溢出 //注意:无 //=====================// UCHAR ShowMenu_Item(UCHAR y,UCHAR* Menu_String,UCHAR Font_Color) { UCHAR *uiTemp; UCHAR uiTemp1; UCHAR i,x,Char_Nmb; x = X_SPACE_FRONT; //Mune show front space.... Char_Nmb = (UCHAR)Menu_String[0]; if(Char_Nmb<0xA1) { for(i=1;i<=Char_Nmb;i++) { uiTemp = (UCHAR*)(Menu_String+i); uiTemp1 = (UCHAR)*uiTemp; if(uiTemp1>128) { FontSet(Font_GB,Font_Color); //选择汉字字库 uiTemp1 = uiTemp1-128; } else { FontSet(Font_String,Font_Color);//选择ASCII码字库 } PutChar(x,y,uiTemp1); x = x+X_Witch;//GetASIIX();
if(x>=Dis_X_MAX) return 0; //横坐标溢出,返回零 } } #ifdef Hz_Lib_II else { FontSet_cn(Font_String,Font_Color); PutString_cn(x,y,(char *)Menu_String); } #endif return 1; } |
局部变量x首先会置为X_SPACE_FRONT的值,而X_SPACE_FRONT也就是在菜单组资源当中定义的配置数组中的菜单项字符偏移X轴原点的点数。 获取要绘制的菜单项的第一个节字,存放于Char_Nmb变量当中。 这里作一下简单的说明,在国标的二级汉字库中,每个汉字的GB码值的高八位和低八位值都是大于0xA1的;所以在此会判断Char_Nmb值是否大于0xA1,如小于则表明当前的菜单GUI使用的是自定义汉字库和LCD驱动中自带的ASCII码西文字库的字符。 绘制菜单项当中的自定义汉字库或者ASCII码字符时,Char_Nmb的值代表该菜单项的字符个数,并以该数值作一个for循环,依次将该菜单项的字符绘制完毕。在前面已介绍过,在定义菜单项时,如使用自定义的汉字库字符,则在定义时在该字符的基础上加上128;所以在代码当中可看到从菜单项的定义数组中读出字符序号数据后会判断是否大于128,如果大于则表示该字符为自定义的汉字库当中的字符,调用FontSet函数选择该字库作为当前字符类型,并调整字符序号值;如小于则选用LCD驱运中的ASCII码西文字库作为当前字符类型。最后调用PutChar函数绘制字符,并对X轴的坐标作出调整,以便显示下一个字符。 在前面介绍Menu_GUI_Config.h文件时,已经介绍过Hz_Lib_II的宏定义表示所使用的LCD是否为自带汉字库的LCD,如果是的话,则Hz_Lib_II有定义,则“#def Hz_Lib_II”和“#endif”之间的代码将会被编译,作为选用自带汉字库的LCD时显示菜单中的汉字的显控代码。
基于MzLH06液晶模块的菜单程序编程(3) |