主要使用 双向链表 结构实现的菜单:
结构体包含7个变量,分别是菜单中功能项的个数,当前菜单标题,菜单中各功能项标题,功能项的类型,然后是函数指针,即索引号对应的函数,最后是指向下一级的结构体指针和指向上一级的结构体指针,第一级菜单的上一级为NULL空。
//定义菜单中功能项的类型
#define TYPE_SUBMENU 101 //具有子菜单的菜单项
#define TYPE_PARAM 102 //参数项(用于执行参数设置)
typedef void (*MENU_FUN)(const char *);
//定义菜单
typedef struct menu
{
uint8_t num; //当前菜单功能项总数
char * title; //当前菜单标题
char * label; //功能项标题
uint8_t type; //当前功能项的类型
MENU_FUN Function; //选择某一功能后执行的功能函数
struct menu *next; //下一级菜单
struct menu *prev; //上一级菜单
} Menu;
//结构体初始化//菜单定义,在这里将每一个菜单的关联设置好
Menu menu1_main[5] = // 第1级 主菜单
{
{5, "主菜单", "文件 >>", TYPE_SUBMENU, NULL, menu2_file, NULL},
{5, "", "编辑 >>", TYPE_SUBMENU, NULL, menu2_edit, NULL},
{5, "", "视图 >>", TYPE_SUBMENU, NULL, menu2_view, NULL},
{5, "", "设置 >>", TYPE_SUBMENU, NULL, menu2_set, NULL},
{5, "", "帮助 --", TYPE_PARAM, NULL, NULL, NULL},
};
Menu menu2_file[4] = // 第2级 文件菜单
{
{4, "文件", "open ", TYPE_SUBMENU, NULL, NULL, menu1_main},
{4, "", "close ", TYPE_SUBMENU, NULL, NULL, menu1_main},
{4, "", "save ", TYPE_SUBMENU, NULL, NULL, menu1_main},
{4, "", "exit ", TYPE_SUBMENU, NULL, NULL, menu1_main},
};
…… //将各级菜单设置好,并且关联好上下页的指针
//省略了,具体参见文章底部的工程百度云链接
绘制当前菜单页的函数:
void DispCrtMenu(void)//绘制当前菜单项
{
uint8_t menu_num = cur_item[0].num;//获取当前菜单的项目数量
uint8_t i, num = menu_num>MENU_MAX_ROW ? MENU_MAX_ROW : menu_num;//绘制数量不能超过每一屏的最大绘制数量
if(item_index>=MENU_MAX_ROW || item_index>=menu_num)//菜单项上下选择是否越界
{
if(item_index==0XFF) //此情况为 item_index=0 再按上键,0-1=0XFF
{
item_index = menu_num - 1; //循环,回到最后一个功能索引值
}
if(item_index>=menu_num) //此情况为到达最下面一个功能索引值
{
item_index = 0; //循环,回到第一个功能索引值
}
if(item_index>=MENU_MAX_ROW)
{
item_index = 0;
}
}
DrawTestPage((u8 *)cur_item[0].title);
for (i=0; i<num; i++)//绘制某一级菜单下的功能键
{
POINT_COLOR = RED;
LCD_ShowString(144,150+(i+1)*40,200,30,24, (u8 *)cur_item[i].label,i==item_index ? 0:1);
}
}
最终功能函数:
//定义菜单操作需要的全局变量
Menu *cur_item = menu1_main; //初始化当前菜单为第一级(menu1_main)
Menu *prev_item = NULL; //初始化上一级菜单为空
uint8_t item_index = 0;//当前菜单项索引
//显示函数
void Display(uint8_t value)
{
if(value==KEY_UP_PRESS || value==KEY_DOWN_PRESS || value==KEY_ENTER_PRESS || value==KEY_RETURN_PRESS)
{
switch(value)//检测按键,进入相应动作
{
case KEY_UP_PRESS:
item_index--;
DispCrtMenu();
break;
case KEY_DOWN_PRESS:
item_index++;
DispCrtMenu();
break;
case KEY_ENTER_PRESS:
switch(cur_item[item_index].type)//检测功能项的类型,进入相应动作
{
case TYPE_SUBMENU: //具有子菜单的菜单项
if(cur_item[item_index].next != NULL)
{
prev_item = cur_item;//此级菜单变成了上一级菜单
cur_item = cur_item[item_index].next;//将指向的下一级菜单设置为当前菜单
item_index = 0;//重置子菜单项索引
DispCrtMenu();
}
else
{
POINT_COLOR = WHITE;
LCD_Fill(0,lcddev.height-40,lcddev.width,lcddev.height,BLUE);
Gui_StrCenter(0,lcddev.height-32,"待设置~~~",24,1);//居中显示
}
break;
case TYPE_PARAM: //具有参数设置的菜单项
if(cur_item[item_index].Function != NULL)
{
//调用相应的动作函数,并传递参数
cur_item[item_index].Function((const char *)cur_item[item_index].label);
}
else
{
POINT_COLOR = WHITE;
LCD_Fill(0,lcddev.height-40,lcddev.width,lcddev.height,BLUE);
Gui_StrCenter(0,lcddev.height-32,"待设置~~~",24,1);//居中显示
}
break;
default:
break;
}
break;
case KEY_RETURN_PRESS:
if (prev_item != NULL)//返回上一级菜单的操作
{
cur_item = prev_item; //设置上一级菜单为当前菜单
prev_item = cur_item[0].prev; //设置当前菜单的上一级菜单
item_index = 0; //重置子菜单项索引
DispCrtMenu();
}
else
{
POINT_COLOR = WHITE;
LCD_Fill(0,lcddev.height-40,lcddev.width,lcddev.height,BLUE);
Gui_StrCenter(0,lcddev.height-32,"已是主菜单",24,1);//居中显示
}
break;
default:
break;
}
}
}
主程序如下:
按键扫描函数,不断检测上下左右的按键值,当检测到按键值,将值传递到显示函数中,执行相应的switch中的case的内容。
int main(void)
{
uint8_t key_value;
uint32_t main_time,key_time;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为115200
TIMER2_Init();
LED_Init(); //初始化LED
KEY_Init();
LCD_Init(); //初始化LCD FSMC接口
POINT_COLOR=RED; //画笔颜色:红色
SetDefaultMenuHandler(Str_Test);//设置功能函数
DispCrtMenu(); //将主菜单页面先显示,去掉此句进入为黑屏
while(1)
{
main_time = Get_RunTime(); //获取系统时间
if(main_time > key_time) //按键扫描,10毫秒1次
{
key_time = main_time;
key_value = KEY_Scan(0);
if(key_value!=0xff)
{
Display(key_value);
}
}
}
}
使用的原子开发板实验的项目,主控为STM32F407ZGT6,4.3寸的TFT 显示屏,未加入触摸功能,只是进行了简单的多级菜单验证,有需要的可以下载参考下,可以移植到OLED、12864等屏幕中做交互界面,只是需要相应地修改显示部分。
整个工程百度云链接:
链接:https://pan.baidu.com/s/10RhsVBQ8PNir_Ey9Sf6uDQ
提取码:ke6y