4. Mz_MenuGUI 菜单应用
4.1. Mz_MenuGUI
在一些带有点阵LCD 显示界面的产品当中,通常会涉及到一些菜单界面的应用,特别是一些带有设置功能的仪器仪表产品;结合自己的设计经验,设计了一个小巧精致的菜单GUI代码,以期将其应用在中小资源的MCU 上,并充分考虑了接不同的MCU 或LCD 模块的扩展能力。
Mz_MenuGUI 实际上就是将菜单显示、响应刷新的代码综合到一起,做成几个精致的通用函数,以减小菜单显示方面的代码量,为小资源的MCU 应用打下基础;而菜单的确切控制还留给用户来做,但这里提供了参考的框架,使用时往里面填代码就可以了。另外,Mz_MenuGUI 是基于前面所介绍的通用LCD 驱动程序的,所以当要换用不同的LCD 模块
实现这样的简单菜单功能时,跟LCD 驱动程序一样非常方便就可以实现移植了;下面了解一下Mz_MenuGUI 的架构情况吧:
此主题相关图片如下61mcu102905.jpg:
Mz_MenuGUI 共有四个文件提供,其中只有一个Menu_GUI.c 是正经八百的C 语言函数代码,其它的三个基本上用于定义与外部声明用;而Menu_Resource.c 当中是定义菜单资源的代码,其实也就是定义几个数组,里面的内容定义好菜单当中显示什么字符以及菜单字符的一些特性定义,这里的定义是要跟LCD 的驱动程序当中的字库相关的定义有联系的,这点会在后面的代码介绍里面分析。
而菜单响应控制方面的代码需要用户自行编写,当然是在这里提供的架构参考的基础之上了,其实也就是做一个按键响应的switch 分支,分别定义什么按键时菜单作什么样的更新操作;这点,我们提供了参考的架构,当然也可以省省事,直接拿来使用也可以。
Mz_MenuGUI 做的菜单界面可以参考以下图片:
此主题相关图片如下61mcu102906.jpg:
4.2. Mz_MenuGUI 的源码分析
4.2.1. Menu_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 显示资源定义文件
// 此代码适用于无byte 操作的16 位MCU,如凌阳的unsp 系列MCU
// 有关汉字字库的资源请参考铭正同创网站上有关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[]={5,0+0x80,1+0x80,2+0x80,3+0x80, 4+0x80};
code unsigned char Menu_String02[]={5,0+0x80,1+0x80,2+0x80,3+0x80, 5+0x80};
code unsigned char Menu_String03[]={5,0+0x80,1+0x80,2+0x80,3+0x80, 6+0x80};
code unsigned char Menu_String04[]={5,0+0x80,1+0x80,2+0x80,3+0x80, 7+0x80};
code unsigned char Menu_String05[]={5,0+0x80,1+0x80,2+0x80,3+0x80, 8+0x80};
code unsigned char Menu_String06[]={5,0+0x80,1+0x80,2+0x80,3+0x80, 9+0x80};
//定义某一组菜单的配置,格式如下:
//{该组菜单的菜单项数目,该组菜单中汉字所选用的字符类型,该组菜单中ASCII 码所选用的类型,
//该组菜单中每条菜单项所占用的Y 轴大小,该组菜单中菜单项显示的X 轴偏移位}
code unsigned char Menu_List01_Config[]={6,3,1,16,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,
(unsigned char *)Menu_String06};
代码前面的地方,定义了单条菜单项的内容,代码的注释里已有格式的简单说明了,用户需要参考前介绍通用LCD 驱动程序当中关于字库的介绍,因为这里的定义是与驱动函数中字库的定义相关的。比如,菜单项Menu_String01 的定义里,采用的是西文字库(ASCII 码)与自定义汉字库相结合的类型,第一个量的意义为该菜单项中有几个字符,第二个量之后的数据为字符在字库中的序号,如果字符是ASCII 码西文字符的话,可以直接用’A’之类的定义即可,编译时会取该字符的ASCII 码值,如果是自定义汉字库的话则要在其序号的基础之上加上0x80(即128),上面的代码中就是例子。(在下面,会介绍这里的菜单GUI 代码所应用的LCD 驱动程序中的字库定义情况)而菜单项要定义多少个就由用户自行选择了,在上面的代码当中,共定义了6 个菜单项,跟显示效果里的一样,分别是:“菜单选项一”、“菜单选项二”、“菜单选项三”、“菜单选项四”、“菜单选项五”、“菜单选项六”。
菜单中使用的汉字字符在驱动里都有了定义,它们在字库里的序号分别是:
“菜”——0
“单”——1
“选”——2
“项”——3
“一”——4
“二”——5
“三”——6
“四”——7
“五”——8
“六”——9
在GB_Table.c 文件当中定义了它们的字库,如下:
code unsigned char GB1616[] =
{
/*-- 文字: 菜 --*/
0x00,0x00,0x0C,0x70,0x0C,0x70,0xFF,0xFF,0x0C,0x70,0x00,0xFC,0x3F,0xCC,0x19,0x98,
0x1D,0xF8,0x0D,0xF0,0xFF,0xFF,0x03,0xC0,0x07,0xE0,0x1D,0xB8,0x39,0x9E,0xE1,0x87,
/*-- 文字: 单 --*/
0x00,0x00,0x18,0x18,0x1C,0x38,0x0C,0x70,0x7F,0xFC,0x61,0x8C,0x61,0x8C,0x7F,0xFC,
0x61,0x8C,0x61,0x8C,0x3F,0xFC,0x01,0x80,0xFF,0xFF,0x01,0x80,0x01,0x80,0x01,0x80,
/*-- 文字: 选 --*/
0x00,0x00,0x00,0x00,0x73,0x60,0x37,0x60,0x3F,0xFE,0x1E,0x60,0x0C,0x60,0x00,0x60,
0xFF,0xFF,0x39,0xB8,0x39,0xB8,0x39,0xBF,0x3B,0x3F,0x3F,0x3E,0x7E,0x1E,0xEF,0xFF,
/*-- 文字: 项 --*/
0x00,0x00,0x00,0x00,0xFF,0xFF,0x3C,0x60,0x33,0xFC,0x37,0x06,0x37,0x66,0x37,0x66,
0x37,0x66,0x37,0x66,0x37,0x66,0x37,0xE6,0x3F,0xC6,0xF1,0xF8,0x03,0x8E,0x0E,0x07,
/*-- 文字: 一 --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/*-- 文字: 二 --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,
/*-- 文字: 三 --*/
0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x3F,0xFC,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,
/*-- 文字: 四 --*/
0x00,0x00,0x00,0x00,0x7F,0xFE,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
0x66,0x66,0x66,0x66,0x6E,0x66,0x78,0x7E,0x60,0x06,0x60,0x06,0x60,0x06,0x7F,0xFE,
/*-- 文字: 五 --*/
0x00,0x00,0x00,0x00,0xFF,0xFE,0x03,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x7F,0xFC,
0x06,0x0C,0x06,0x0C,0x06,0x0C,0x06,0x0C,0x0E,0x0C,0x0E,0x0C,0x0C,0x0C,0xFF,0xFF,
/*-- 文字: 六 --*/
0x00,0x00,0x00,0x00,0x03,0x80,0x01,0x80,0x01,0xC0,0x00,0xC0,0xFF,0xFF,0x00,0x60,
0x0E,0x70,0x0C,0x38,0x0C,0x18,0x1C,0x1C,0x18,0x0C,0x38,0x0E,0x70,0x06,0xE0,0x07
};
定义好字库后同时也要在LCD_Dis.c 当中为新的字库修改一定的代码,在该文件的前面加上对这个字库的外部声明,如下:
extern code unsigned char Asii0610[]; //6X10 的ASII 字符库
extern code unsigned char GB1616[]; //16X16 的自定义字符库
extern code unsigned char GB1716[]; //17*16 自定义的汉字库
然后在FontSet 函数中加上该字库所对应的配置代码,如下:
void FontSet(unsigned char Font_NUM,unsigned char Color)
{
switch(Font_NUM)
{
case 1: Font_Wrod = 10; //ASII 字符B
X_Witch = 6;
Y_Witch = 10;
Char_Color = Color;
Char_TAB = (unsigned char *)(Asii0610 - (32*10));
break;
case 2: Font_Wrod = 48; //汉字A
X_Witch = 17;
Y_Witch = 16;
Char_Color = Color;
Char_TAB = (unsigned char *)GB1716;
break;
case 3: Font_Wrod = 32; //汉字B
X_Witch = 16;
Y_Witch = 16;
Char_Color = Color;
Char_TAB = (unsigned char *)GB1616;
break;
default: break;
}
}
接下来定义了一个数组,用于一组菜单的配置,也就是说可以在程序里面定义多组菜单,然后每一组菜单都可以拥有自己的特性配置。下面看看这个配置的数组里具体定义的含意。
Menu_List01_Config[]={6,3,1,16,10}其中定义了5 个数据,第一个数据为声明该组菜单当中共有多少个菜单项;比如在前面的介绍中得知,这个菜单Demo 里面共定义了六个菜单项,所以在这里将该数值设置为6;接下来的一个数据代表混合字符菜单项里的汉字字库序号,能过前面介绍的代码可知,定义为3;接着便是ASCII 码西文字库的序号,为1。
而其中的数据16 表示该组菜单当中每一个菜单项所占用的Y 轴的点数,通常取该组菜单当中的字符里Y 轴方面最大的值;这里可知前面定义的菜单项里面采用的是16*16 点阵的字符,所以该值定义为了16。最后的一个数据表示菜单项显示时,对X 轴原点的固定偏移数;可以从下图中看出在数组中定义的10 的意义:
此主题相关图片如下61mcu102907.jpg:
如上图所示意的,如果想要菜单项的字符从X 轴为0 的地方开始显示的话,只需要将菜单配置数组中的最后一个数设为0 即可。
最后一个定义是对一组菜单的定义了,用户在菜单响应的控制程序里会使用到该数组,一组菜单定义对应一个菜单界面,如果有多级菜单的话,可以定义多组菜单,格式参考代码里的定义即可。这组数据当中,第一个量为该组菜单的配置,接下去的数据为该组菜单当中的每个菜单项的定义。
注意:可能不同的编译器对指针的编译会有所不同,包括常量的定义,这点请用户注意,要移植该代码时可能要作出适量的修改。
4.2.2. Menu_GUI_Config.h 菜单GUI 配置头文件
菜单GUI 的配置头文件里面定义了菜单的字符色等,下面是源码:
// write your header here
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 模块而作的扩展定义。
4.2.3. Menu_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)函数是获取一组菜单资源里面的菜单项个数的,传递进该函数的参数就是上一小节当中所说的一组菜单的定义数组,返回的是该组菜单资源当中的菜单项个数;不过要注意的是跟前面介绍的定义有点不一样,在前面介绍时,如果菜单项个数为6 个的话,在定义里面就直接将菜单资源配置数组里的第一个量置为6 了,这里的返回值也是读取了该数据,但减去了1,因为菜单项的序号从0 开始。
UCHAR GetMLiNum_Page(UCHAR** Menu_List)函数就与LCD 驱动程序当中的配置有关了,它的功能是获取一屏LCD 显示当中共能显示菜单项数量,代码中使用了Dis_Y_MAX的配置,并除以菜单项的Y 轴点数,得到的是一屏LCD 显示能容下的菜单项数。
在菜单显示的代码里面,定义了几个全局的变量,用于更新显示时使用,如下:
#include "./Driver/LCD_Driver/LCD_Dis.h"
#include "./Driver/LCD_Driver/LCD_Config.h"
#include "./MzMenu_GUI/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(0); //清屏
SetPaintMode(1,MENU_SELE_COLOR);
if(y_Index_old==0xff) ③
{
Rectangle(0,0,Dis_X_MAX, 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[i],
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[i],
MENU_SELF_COLOR); ⑥
else ShowMenu_Item(y_Index,(UCHAR *)Menu_List[i],
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; //Menu 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,(unsigned short *)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 时显示菜单中的汉字的显控代码。
注意:Mzdesign 的自带汉字库的LCD 在驱动程序里提供有FontSet_cn 和PutString_cn 这两个函数,而本书所介绍的LCD 通用驱动程序当中是没有这两个函数的,这点请读者参考Mzdesign 的代码。
4.3. 定制自己的Menu 菜单界面
4.3.1. 参考的GUI 响应控制代码
为了方便使用,配合着Mz_MenuGUI 代码提供了一份参考的响应控制代码,用户可以参照它的架构编写自己需要的控制代码。
当然,菜单的使用一般离不开键盘,在这里就使用了键盘扫描程序,但仅供参考,用户可以根据自己的情况编写合适的键盘扫描程序,并定义合适的键值作为菜单响应控制的控制键。
首先,菜单响应控制代码需要定义几个变量,如下:
//add your code here
unsigned char uiKey=0;
//uiKey 用于存放扫描的键值状态
unsigned char Item_Num,Update_Flag,Enter_Flag=0;
//Item_Num:当前菜单组当中共有几项菜单项,刷新时使用
//Update_Flag:菜单界面刷新标识
//Enter_Flag:确定键按下标识~~
unsigned char PageItem_Num;
//PageItem_Num:每页(全显示屏内)可以显示多少个菜单项
unsigned char First_Index=0,Active_Index=0,Temp_Index=0;
//First_Index:当前处在显示屏当中的最前面的菜单项序号
//Active_Index:当前指向的菜单项,即当前活动的菜单项
//Temp_Index:中间变量
unsigned char Exit_flag=1; //菜单响应循环退出标识
上面定义的变量当中,有几个是用于暂存菜单组资源中的一些数据的,比如Item_Num、PageItem_Num 等,其实在前面介绍的菜单显示控制的代码(Menu_GUI.c)当中已有定义了,这里再定义实际上是为多级菜单的使用而做的;因为在Menu_GUI.c 当中定义的变量只能供当前选用的菜单组使用,如有多级菜单时,相互之间有层层的调用关系,单靠其中的变量是无法得知全部的菜单组资源的参数的。由此,建议每个菜单界面都应定义上述的变量,以供当前的菜单响应控制使用。
Exit_flag 变量为循环退出标识,将在菜单响应控制的循环当中控制着是否继续下去,该变量被设置为0 时,表示要退出当前的菜单。当然,如用户的菜单只有一层,而且始终在使用该菜单的话,可以不使用这个变量,直接在菜单响应控制的循环当中用1 替代它即可。
菜单响应控制代码当中要使用到一些函数,所以在其前面需对一些外部头文件进行包含声明。如下:
#include "./Driver/LCD_Driver/LCD_Dis.h"
#include "./Driver/Key_Service/Key.h"
#include "./MzMenu_GUI/Menu_GUI.h"
Key.h 是键盘扫描程序的头文件,这里不作说明。
LCD_Dis.h 为通用版的LCD 驱动程序的用户接口程序头文件,可视情况而选择是否使用它(至少在用户的菜单响应控制代码当中是可以选择的)。
菜单响应控制的参考代码如下:
Key_Initial(); //键盘扫描初始化(端口)
LCD_Init(); //初始化LCD
Item_Num = GetMLNum(Menu_List01); //获取要显示的菜单的菜单项个数
PageItem_Num = GetMLiNum_Page((unsigned char **)Menu_List01); //获取要显示的菜单
//的页数
Initial_Menu((unsigned char**)Menu_List01);
while(Exit_flag)
{
uiKey = Key_Get(); //获取键值
if(uiKey) //如有键按下则响应
{
switch(uiKey) //判断键值进行分支控制界面
{
case 3: //确定键按下
Enter_Flag=1; //enter flag set
break;
case 2: //down 向下键按下
if(Active_Index<Item_Num)
{
Active_Index++;
if(Temp_Index<PageItem_Num-1) Temp_Index++;
else if(First_Index<Item_Num) First_Index++;
Update_Flag = 1;
}
break;
case 1: //up 向上键按下
if(Active_Index>0)
{
Active_Index--;
if(Temp_Index>0) Temp_Index--;
else if(First_Index>0) First_Index--;
Update_Flag = 1;
}
break;
/* case 4: //返回键按下
Exit_flag = 0;//
break;*/
default:break;
}
}
if(Update_Flag) //Update_Flag 为1 时刷新菜单
{
UpDate_Menu(First_Index,Active_Index,(unsigned char**)Menu_List01);
Update_Flag=0;
}
if(Enter_Flag) //有确定键按下时,则进入相应的功能函数
{
Enter_Flag = 0;
switch(Active_Index)
{
case 0: //这里仅定义了可以响应第一项菜单
// Show_DemoTast();
// Redraw_Menu(First_Index,Active_Index,(unsigned char**)Menu_List01);
// Update_Flag = 1;
break;
case 1: break; //如感兴趣可以自己加进去玩玩
case 2: break;
case 3: break;
case 4: break;
case 5: break;
default:break;
}
}
KeyScan_Service(); //键盘扫描的服务程序,最好在1KHz 的定时/时基中断调用
}
Key_Initial()是键盘扫描程序初始化函数,用户可根据自己的键盘扫描程序来修改这块的代码,而如果在之前已经进行过键盘扫描的初始化,则此处没有必要再重新初始化;比如在一个二级的菜单响应控制代码当中,它是由上一级的菜单响应控制代码响应操作而进入的,这时就无需再初始化扫键了。
LCD_Initil 函数的调用也如此,如果是在二级菜单的响应控制代码中的话,是无需进行第二次初始化的。
接下来,调用Menu_GUI.c 中的函数来初始化变量Item_Num 和PageItem_Num 的数值,注意,在调用相关的菜单GUI 显示控制函数时,都要传递当前选择的菜单组资源到调用的函数当中的。有些编译器对数据的类型定义要求较严,这时在传递参数时需要对参数进行类型声明或强制转换。
每一个菜单组资源在使用时,在该菜单组的响应控制代码当中都要对其进行初始化,也就是调用Initial_Menu 函数。
从前面的代码中可以看出,这份示例的菜单响应控制代码使用的菜单组资源是Menu_List01,它的定义在前面已经介绍过了。
while(Exit_flag)定义了菜单响应控制的循环,标志变量Exit_flag 决定着这个循环是否继续下去。
Key_Get()函数可以获取到当前有效的按键值,当然,用户使用自己定义的键盘扫描程序时,应视自己的代码而定,反正只要能够获取到有效的按键值就可以了。获取的键值存放于uiKey 变量当中;需要注意的是,在没有键按下时,获取键值的函数应返回0 或其它的数值以区别有效的键值。
在上面的代码当中,当有效的按键按下时,uiKey 的值为非零,这时会进行一个Switch 的分支判断,以响应不同的按键。在代码当中,定义了uiKey 的键值为1、2、3 时的按键含意,也就是按键的作用;键值1 为菜单项向上移动,键值2 为菜单项向下移动,键值3 为确定键,而在代码当中,有一段屏蔽掉的代码,为退出键的定义的,定义的键值为4。
当键值为1 时,会调整当前活动的菜单项,并置标志变量Update_flag 为1,让程序在循环当中刷新显示的菜单。
键值为2 时,也调整当前活动的菜单项,当然是向下调整了,同时置标志变量Updage_flag为1,以控制刷新显示的菜单。
键值为3 时,为确定键按下,置Enter_flag 为1,在后面的代码当中会进行相对应的菜单项响应操作分支。
当键值分支处理代码执行结束后,循环当中会判断当前的显示是否需要刷新,即判断Update_flag 的值,如果需要刷新,则调用UpDate_Menu 函数。
随后的代码中,判断Enter_flag 即确定按键是否按下,如按下,则进入菜单响应控制的分支处理;在switch 分支中,根据Active_Index 的值判断当前处于选择状态的菜单项是哪一个,
然后进行分支处理,也就是每个case 分支对应一个菜单项的响应,这里用户可以根据自己的需要进行代码的编写;例如在上面的代码当中就有一段屏蔽的代码,其响应第一项菜单项,调用了一个Show_DemoTask 函数进入了一个显示界面(具体显示什么无所谓了,仅供参考),从该函数退出后,需要调用一个Redraw_Menu 函数,重绘当前的菜单显示,然后置显示刷新标识Update_flag 为1,在下一次循环中完成刷新显示。
每次循环里,都调用一个键盘扫描的函数:KeyScan_Service(),其实可以根据用户自己的键盘扫描程序架构而定的,这里仅供参考而已。
4.3.2. 订制一个有二级菜单的工程
先简单介绍一下这个有二级菜单的工程的一些要求,如下:
要求一级菜单有五项菜单项,分别为:
- 绘点
- 绘直线
- 绘矩形
- 字符演示
- 帮助
而字符演示这项的菜单响应有二级的菜单,即要求选择该项菜单项时,按下确定按键时会调出二级菜单,二级菜单要求有以下几项:
- 单个西文字符
- 西文字符串
- 中文字符
- 返回上一级
而在一级菜单的菜单项响应时,绘点这项就会在屏幕上绘制一个点,绘直线就在屏上绘制直线,基本上跟菜单中文字是意思相同的,只有“字符演示”这一项是展开二级菜单的;类似,在二级菜单当中,每一项的响应也是与其名字意义相同。
而设定以上的菜单项中的文字都是16*16 点阵的汉字,而且事选都取好了字模放置在自定义的汉字库当中,并且都在Font_Set 函数当中修改好了相关的配置;而定义好的汉字在字库中的序号分别为:
“绘点直线矩形字符演示帮助单个西文串中返回上一级”这些字符的序号从0~22 依次排列;而字库的类型序号为3。
接下来就可以进行菜单系统的设计了,首先对于Menu_GUI 的源码,只需要修改一部分的内容就可以了,也就是菜单资源的定义,即Menu_Resource.c 中的定义。
修改的代码如下:
//定义单条菜单项内容,格式有两种,一种为支持汉字库LCD 的纯汉字菜单项,一种是西文字符与自定
//义汉字库的混合菜单项的如下:
//一:直接用汉字的字串即可,不同的编译器可能在汉字的GB 码数据类型上有所不一样
//二:菜单项字符数,第一个字符在字库中的序号,第二个字符,....
//注:在第二种情况下,为了区分自定义汉字与ASCII 码,特定将自定义汉字库中的汉字编码前加上
// 128 作为标识
code unsigned char Menu_String01[]={2,0+0x80,1+0x80};
code unsigned char Menu_String02[]={3,0+0x80,2+0x80,3+0x80};
code unsigned char Menu_String03[]={3,0+0x80,4+0x80,5+0x80};
code unsigned char Menu_String04[]={4,6+0x80,7+0x80,8+0x80,9+0x80};
code unsigned char Menu_String05[]={2,10+0x80,11+0x80};
code unsigned char Menu_String06[]={6,12+0x80,13+0x80,14+0x80,15+0x80,6+0x80,7+0x80};
code unsigned char Menu_String07[]={5,14+0x80,15+0x80,6+0x80,7+0x80,16+0x80};
code unsigned char Menu_String08[]={4,17+0x80,15+0x80,6+0x80,7+0x80};
code unsigned char Menu_String09[]={5,18+0x80,19+0x80,20+0x80,21+0x80,22+0x80};
//定义某一组菜单的配置,格式如下:
//{该组菜单的菜单项数目,该组菜单中汉字所选用的字符类型,该组菜单中ASCII 码所选用的类型,
//该组菜单中每条菜单项所占用的Y 轴大小,该组菜单中菜单项显示的X 轴偏移位}
code unsigned char Menu_List01_Config[]={5,3,1,16,10};
code unsigned char Menu_List02_Config[]={4,3,1,16,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};
code unsigned char *Menu_List02[]=//
{(unsigned char *)Menu_List02_Config,(unsigned char *)Menu_String06,
(unsigned char *)Menu_String07,(unsigned char *)Menu_String08,
(unsigned char *)Menu_String09};
然后在Menu_GUI.h 文件当中,添加定义的菜单组资源数组的声明,如下:
extern code UCHAR *Menu_List01[];
extern code UCHAR *Menu_List02[];
随后可以编写响应菜单项的功能函数,其实在这里也就是一些显示的演示程序,没有太多实际的意义,仅供参考,用户根据自己设计的需要来自行设计。
一级菜单的各项菜单项响应函数大概如下:
“绘点”菜单项,对应一绘点的演示界面,代码如下:
void Show_DotTest(void)
{
unsigned int Key=0;
unsigned Exit_flag=1;
ClrScreen(0); //清屏
SetPaintMode(0,1); //设置绘图模式及前景色
PutPixel(0,0); //绘制点
PutPixel(2,0);
PutPixel(4,0);
PutPixel(6,0);
PutPixel(8,0);
PutPixel(9,0);
PutPixel(10,0);
PutPixel(0,2);
PutPixel(0,4);
PutPixel(0,6);
PutPixel(0,8);
PutPixel(0,10);
while(Exit_flag)
{
Key = Key_Get();
if(Key!=0)
Exit_flag = 0; //任意键按下则返回
KeyScan_Service();
}
}
“绘直线”菜单项对应的函数代码如下:
void Show_LineTest(void)
{
unsigned int Key=0;
unsigned Exit_flag=1;
ClrScreen(0); //清屏
SetPaintMode(0,1); //设置绘图模式及前景色
Line(127,63,0,63); //绘制一条直线
Line(10,12,10,42);
while(Exit_flag)
{
Key = Key_Get();
if(Key!=0)
Exit_flag = 0; //任意键按下时,退出返回
KeyScan_Service();
}
}
“绘矩形”菜单项对应的函数源代码如下:
void Show_RectanglTest(void)
{
unsigned int Key=0;
unsigned Exit_flag=1;
ClrScreen(0); //清屏
SetPaintMode(0,1); //设置绘图模式及前景色
Rectangle(12,12,42,42,1); //矩形填充
Rectangle(52,12,82,42,0); //绘制矩形框
while(Exit_flag)
{
Key = Key_Get();
if(Key!=0)
Exit_flag = 0; //任意键按下时,退出返回
KeyScan_Service();
}
}
“帮助”菜单项的响应函数里将显示一串字符串,源代码如下:
void Show_HelpTest(void)
{
unsigned int Key=0;
unsigned Exit_flag=1;
ClrScreen(0); //清屏
SetPaintMode(0,1); //设置绘图模式及前景色
FontSet(1,1); //设置字体类形,字符色为1
PutString(5,10,"Wellcome to MzDesign!!"); //显示字符串
PutString(5,50,"www.mzdesign.com.cn");
Line(4,60,120,60); //绘制一条直线
while(Exit_flag)
{
Key = Key_Get();
if(Key!=0)
Exit_flag = 0; //任意键按下时,退出返回
KeyScan_Service();
}
}
“字符演示”的菜单项将来打开二级菜单,所以暂时将该菜单项的响应函数放置到后面,先将二级菜单当的各项菜单项对应的功能演示函数介绍。
二级菜单中,“单个西文字符”菜单项的响应函数定义为:Show_CharTest(),而菜单项“西文字符串”的响应函数定义为:Show_StringTest(),“中文字符”菜单项的响应函数定义为:
Show_ChTest()。以上几个函数就不作详细的介绍了,而菜单项“返回上一级”的响应时无需调用函数那么复杂,可以在其的分支当中将菜单循环标志变量置为0 即可实现。下面看一下在这个二级菜单的响应控制代码,也就是一级菜单中的菜单项“字符演示”所对应的响应函数:
void CharTest_Menu(void)
{
unsigned char uiKey=0;
unsigned char Item_Num,Update_Flag,Enter_Flag=0;
unsigned char PageItem_Num;
unsigned char First_Index=0,Active_Index=0,Temp_Index=0;
Item_Num = GetMLNum(Menu_List02); //获取要显示的菜单的菜单项个数
PageItem_Num = GetMLiNum_Page((unsigned char **)Menu_List02);//获取显示的菜单的页数
Initial_Menu((unsigned char**)Menu_List02);
while(1)
{
uiKey = Key_Get(); //获取键值
if(uiKey) //如有键按下则响应
{
switch(uiKey) //判断键值进行分支控制界面
{
case 3: //确定键按下
Enter_Flag=1; //enter flag set
break;
case 2: //down 向下键按下
if(Active_Index<Item_Num)
{
Active_Index++;
if(Temp_Index<PageItem_Num-1) Temp_Index++;
else if(First_Index<Item_Num) First_Index++;
Update_Flag = 1;
}
break;
case 1: //up 向上键按下
if(Active_Index>0)
{
Active_Index--;
if(Temp_Index>0) Temp_Index--;
else if(First_Index>0) First_Index--;
Update_Flag = 1;
}
break;
default:break;
}
}
if(Update_Flag) //Update_Flag 为1 时刷新菜单
{
UpDate_Menu(First_Index,Active_Index,(unsigned char**)Menu_List02);
Update_Flag=0;
}
if(Enter_Flag) //有确定键按下时,则进入相应的功能函数
{
Enter_Flag = 0;
switch(Active_Index)
{
case 0: //”单个西文字符”菜单项
Show_CharTest();
Redraw_Menu(First_Index,Active_Index,(unsigned char**)Menu_List02);
Update_Flag = 1;
break;
case 1: //”西文字符串”菜单项
Show_StringTest();
Redraw_Menu(First_Index,Active_Index,(unsigned char**)Menu_List02);
Update_Flag = 1;
break;
case 2: // ”中文字符”菜单项
Show_ChTest();
Redraw_Menu(First_Index,Active_Index,(unsigned char**)Menu_List02);
Update_Flag = 1;
break;
case 3: Exit_flag = 0;break; //”返回上一级”菜单项
default:break;
}
}
KeyScan_Service(); //键盘扫描的服务程序,最好在1KHz 的定时/时基中断调用
}
}
然后再来看看一级菜单的源代码;在此一级菜单直接在main 函数中循环响应,仅供参考,
如下:
void main(void)
{
unsigned char uiKey=0;
unsigned char Item_Num,Update_Flag,Enter_Flag=0;
unsigned char PageItem_Num;
unsigned char First_Index=0,Active_Index=0,Temp_Index=0;
Key_Initial(); //键盘扫描初始化(端口)
LCD_Init(); //初始化LCD
Item_Num = GetMLNum(Menu_List01); //获取要显示的菜单的菜单项个数
PageItem_Num = GetMLiNum_Page((unsigned char **)Menu_List01);//获取显示的菜单的页数
Initial_Menu((unsigned char**)Menu_List01);
while(1)
{
uiKey = Key_Get(); //获取键值
if(uiKey) //如有键按下则响应
{
switch(uiKey) //判断键值进行分支控制界面
{
case 3: //确定键按下
Enter_Flag=1; //enter flag set
break;
case 2: //down 向下键按下
if(Active_Index<Item_Num)
{
Active_Index++;
if(Temp_Index<PageItem_Num-1) Temp_Index++;
else if(First_Index<Item_Num) First_Index++;
Update_Flag = 1;
}
break;
case 1: //up 向上键按下
if(Active_Index>0)
{
Active_Index--;
if(Temp_Index>0) Temp_Index--;
else if(First_Index>0) First_Index--;
Update_Flag = 1;
}
break;
default:break;
}
}
if(Update_Flag) //Update_Flag 为1 时刷新菜单
{
UpDate_Menu(First_Index,Active_Index,(unsigned char**)Menu_List01);
Update_Flag=0;
}
if(Enter_Flag) //有确定键按下时,则进入相应的功能函数
{
Enter_Flag = 0;
switch(Active_Index)
{
case 0: //”绘点”菜单项
Show_DotTest();
Redraw_Menu(First_Index,Active_Index,(unsigned char**)Menu_List01);
Update_Flag = 1;
break;
case 1: //”绘直线”菜单项
Show_LineTest();
Redraw_Menu(First_Index,Active_Index,(unsigned char**)Menu_List01);
Update_Flag = 1;
break;
case 2: //”绘矩形”菜单项
Show_ RectanglTest();
Redraw_Menu(First_Index,Active_Index,(unsigned char**)Menu_List01);
Update_Flag = 1;
break;
case 3: //进入二级菜单
CharTest_Menu();
Redraw_Menu(First_Index,Active_Index,(unsigned char**)Menu_List01);
Update_Flag = 1;
break;
case 4: //”帮助”菜单项
Show_HelpTest();
Redraw_Menu(First_Index,Active_Index,(unsigned char**)Menu_List01);
Update_Flag = 1;
break;
default:break;
}
}
KeyScan_Service(); //键盘扫描的服务程序,最好在1KHz 的定时/时基中断调用
}
}
到这里,基本上就完成了在Mz_MenuGUI 的基础之上搭建一个具有二级菜单的程序框架;其实介绍它的目的在于分绍一种根据显示界面而定下程序框架的编程方法,并非在于手把手的教读者去设计一个这样的程序,这点希望读者明白,重要的是编程的思想不在于步骤。