首先,介绍一下菜单的结构
其大概就是这个样子的,一个方框就是一个单元,单元的内容主要包括两个方面:父-表示这一块的Title,子-表示具体选项(父系社会)。然后,为了方便可以添加一些附加信息-就像最起码要记得子女的个数。具体使用C语言定义一个结构体如下:
struct _MENU_FATHER;//使用在定义之前,所以需要在这里进行一下声明,叫做不完全声明
typedef struct _MENU_SON
{
INT8U name[16]; // 菜单项目名称,儿子的小名,像“大儿子”、“二儿子”这样子
INT8U style; //Style='m'表示指向的是个菜单menu,Style='p'代表表示指向的是个优先级priority
union //m表示指向的是绿色方框,p表示指向的是黄色方框
{
struct _MENU_FATHER *menuSon; //就是指向绿色Title的箭头
int prio; //就是指向黄色方框的箭头
}Category;
} MENU_SON;
typedef struct _MENU_FATHER
{
INT8U title[16]; //言明上承何处,父亲的名字,当然要尊称大名,就全部用大写字母代替
MENU_SON *menuson; //子菜单的地址,大儿子的住处,在上图中就是红色大箭头
INT8U line; //正好表示最后一个子菜单在数组中的序号,子女的个数-1
}MENU_FATHER;
任意一个基础单元只会有一个父类,却可以有多个子类,这里规定子类只有两种情况(儿子or女儿),接下来就开始进行具体的工作
/*********************************全局变量的定义*******************************************/
u8 CURSOR=0,PAGE=0;//用于记录当前LCD显示的页数与行数
//CURSOR(0-2)代表当前LCD中的光标的位置,0表示光标在第一行,注意父类的名字永远搞怪在前头
//PAGE表示当前的页数,Index(就是子菜单的地址)=PAGE*3+CURSOR
/************************************结构体变量定义******************************************/
MENU_FATHER *TitleCur;//当前的菜单控制块指针,表示当前访问的是哪一家
MENU_FATHER *MenuTbl[3];//假设有3层菜单,抄袭uC/OS操作系统的OSPrioTbl
//用于记录历史,这样容易原路返回,你进了子菜单处理完了数据后,肯定要回到主菜单的啊
INT8U CurrentLevel=0;//用于记录当前的菜单层次,默认是主菜单=0
MENU_FATHER MainMenuTitle;//主菜单,是这个菜单的头部
MENU_FATHER DisplayTitle,ConfigTitle;//第一层菜单
做完这些基本工作之后,就可以开始着手准备建立一个自己的多层菜单界面了
首先,写好各个界面。一共分为两层。
第一层主界面定义如下
MENU_SON MainMenuBody[]=//为父已经在前面已经定义了,这里是定义and赋值孩儿们
{
{"Data Display" ,'m' ,.Category.menuSon=&DisplayTitle },//
{"Config Info" ,'m' ,.Category.menuSon=&ConfigTitle },
{"LCD Adjust" ,'p' ,.Category.prio =LCD_ADJUST_PRIO },
{"LCD Adjust1" ,'p' ,.Category.prio =LCD_ADJUST_PRIO },//后面这些主要是用来演示用的
{"LCD Adjust2" ,'p' ,.Category.prio =LCD_ADJUST_PRIO },
{"LCD Adjust3" ,'p' ,.Category.prio =LCD_ADJUST_PRIO },
{"LCD Adjust4" ,'p' ,.Category.prio =LCD_ADJUST_PRIO },
{"LCD Adjust5" ,'p' ,.Category.prio =LCD_ADJUST_PRIO },
{"LCD Adjust6" ,'p' ,.Category.prio =LCD_ADJUST_PRIO },
{"LCD Adjust7" ,'p' ,.Category.prio =LCD_ADJUST_PRIO }
};
然后,组建一个家庭
/*******************************************************************************
* FunctionName : MainMenuTitleInit()
* Description : 主菜单初始化
*****************************************************************************/
static void MainMenuTitleInit(void)//主菜单
{
u8 i=0;
char *p=" -= MENU =-";
while((*p)!='\0'){
MainMenuTitle.title[i++]=*(p++);
}
MainMenuTitle.title[i]='\0';
/*******MainMenuTitle菜单赋值****/
MainMenuTitle.menuson=MainMenuBody;
MainMenuTitle.line=sizeof(MainMenuBody)/sizeof(MainMenuBody[0])-1;//正好表示最后一个子菜单在数组中的序号
}
依次对另外两个style=‘m’的进行组建家庭
/*************************定义与赋值************************/
MENU_SON DisplayBody[]=
{
{"RS485" ,'p' ,.Category.prio = RS_DISP_PRIO },
{"NRF" ,'p' ,.Category.prio = NRF_DISP_PRIO },
{"SI4463",'p' ,.Category.prio = SI_DISP_PRIO }
};
MENU_SON ConfigBody[]=
{
{"RS485" ,'p' ,.Category.prio = RS_CONF_PRIO },
{"NRF" ,'p' ,.Category.prio = NRF_CONF_PRIO },
{"SI4463",'p' ,.Category.prio = SI_CONF_PRIO }
};
/*************************组建家庭************************/
static void DisplayTitleInit(void)//
{
u8 i=0;
char *p="DISPLAY";
while((*p)!='\0'){
DisplayTitle.title[i++]=*(p++);
}
DisplayTitle.title[i]='\0';
DisplayTitle.menuson=DisplayBody;
DisplayTitle.line=sizeof(DisplayBody)/sizeof(DisplayBody[0])-1;
}
static void ConfigTitleInit(void)//
{
u8 i=0;
char *p="CONFIG";
while((*p)!='\0'){
ConfigTitle.title[i++]=*(p++);
}
ConfigTitle.title[i]='\0';
ConfigTitle.menuson=ConfigBody;
ConfigTitle.line=sizeof(ConfigBody)/sizeof(ConfigBody[0])-1;
}
看到这里,也许你会想,为什么不把“组建家庭”写成一个函数呢,这样不是方便多了吗?
其实我也这么想过,最后得出的结论是answer
/**@Q:这里为什么不写成函数的形式而是采用一个一个的赋值呢,
因为指针和数组名是不同的结构,他们sizeof的结果完全不同****/
好的,接下来就是整合一下
/*******************************************************************************
* FunctionName : MenuInit()
* Description : 菜单初始化函数
*****************************************************************************/
void MenuInit(void)
{
MainMenuTitleInit();
ConfigTitleInit();
DisplayTitleInit();
MenuTbl[0]=&MainMenuTitle;//MenuTbl是用来记录多层次菜单路径的,MenuTbl[0]里就是主菜单,无法动摇
TitleCur=&MainMenuTitle;//开始当然是从主菜单开始显示
CurrentLevel=0;//默认主菜单的level=0
}
接着就是编写按键的处理程序
/*****************************************************************************
* FunctionName : Key_Up()
* Description : 上键的处理函数
*****************************************************************************/
void Key_Up(void) // 向上
{
if(CURSOR!=0)//能不能上天?
CURSOR--;
else{//CURSOR=0,已经到达了该页的顶部
if(PAGE!=0){//如果上到了第一项,但是还没有到达第一页,这时候就要从上一页底部重新开始
PAGE--;//上一页
CURSOR=2;//底部
}
else{//PAGE=1,到达首项
PAGE=TitleCur->line/3;//切换到底部
CURSOR=TitleCur->line%3;//最后一项
}
}
}
/*******************************************************************************
* FunctionName : Key_Down()
* Description : 下键的处理函数
*****************************************************************************/
void Key_Down(void) // 向下
{
CURSOR++;
if(PAGE>=TitleCur->line/3){//如果是最后一页
if(CURSOR>TitleCur->line%3){//如果最后一页还超过了最后一项,就重头开始
PAGE=0;
CURSOR=0;
}
}
else
{
if(CURSOR>2){//如果光标超过了屏幕就说明要换页了
PAGE++;
CURSOR=0;
}
}
}
/*******************************************************************************
* FunctionName : Key_Enter()
* Description : 确认键的处理函数
*****************************************************************************/
void Key_Enter(void)
{
//MENU_FATHER *TitleCur;//当前的菜单控制块指针
//MENU_FATHER *MenuTbl[5];//假设有5层菜单,我感觉已经足够了
//INT8U CurrentLevel=0;//用于记录当前的菜单层次,默认是主菜单=0
if(TitleCur->menuson[PAGE*3+CURSOR].style=='m'){
CurrentLevel++;//指示当前的菜单增加了一个层次
if(CurrentLevel>7)CurrentLevel=7;//前面假设最多有8层菜单,我感觉已经足够了
MenuTbl[CurrentLevel]=TitleCur->menuson[PAGE*3+CURSOR].Category.menuSon;//记录当前菜单,方便退出
TitleCur=MenuTbl[CurrentLevel];//加载新的菜单
PAGE=0;CURSOR=0;//进入新的一页
}
else if(TitleCur->menuson[PAGE*3+CURSOR].style=='p'){
Switch2Son(TitleCur->menuson[PAGE*3+CURSOR].Category.prio);//执行相关函数
}
}
/*******************************************************************************
* FunctionName : Key_Esc()
* Description : 退出键的处理函数
*****************************************************************************/
void Key_Esc(void)
{
if(CurrentLevel!=0){
MenuTbl[CurrentLevel]=(MENU_FATHER *)0;//先把原来被占用的清除
CurrentLevel--;
TitleCur=MenuTbl[CurrentLevel];
PAGE=0;CURSOR=0;//不具有记忆功能,一起从头开始
}
}
/*******************************************************************************
* FunctionName : Switch2Son()
* Description : 挂起当前任务,切换到子菜单指向的任务
*****************************************************************************/
void Switch2Son(int TaskResume)
{
OSTaskResume(TaskResume);//挂起当前任务
OSTaskSuspend(OS_PRIO_SELF);//切换到新任务
}