多级菜单是一种用户界面设计,它将信息和选项组织为层次结构,使得用户可以快速找到所需的选项。多级菜单的实现基于两种方案索引法和树结构法,索引法阅读性好,扩展性不错,查找性最优,但是比较占用内存,并且一旦选项过多就会造成逻辑混乱
下面展示使用索引法设计的多级目录(黑色的板子是底板,可以理解为面包板),首先是一级目录:

其次,二级目录:

最后,三级目录:

在使用多级界面之前,首先要了解一些基础知识。typedef是C/C++中的一个关键字,用来给一个已存在的数据类型(比如int、float、struct等)取一个新的别名(自定义数据类型)。通过typedef声明的别名,可以像原类型一样被使用,但具有更直观、更易读的含义,从而提高代码可读性和可维护性。typedef的使用格式为:
typedef 原类型 新类型
举个例子,如:
typedef int MyInt;
MyInt a = 10;//等效int a = 10
再看这个例子,将一个结构体定义为Point类型的别名,使用时就可以使用Point代替这个结构体。这个例子中的第三行代码创建了一个Point类型的结构体变量p,并为其成员变量赋值,除此之外,还有更多的定义方式,如数组、函数指针、enum等
typedef struct {
int x;
int y;
} Point;
Point p;
p.x = 1;
p.y = 2;
简单来说,函数指针是指向函数的指针变量,它存储了函数的入口地址,可以用来调用函数。在C语言中,函数指针的定义格式为:返回值类型 (*函数指针名称)(参数列表)。
在下面的例子中,定义了一个函数指针func_ptr,它指向同样返回int类型的函数add。将add函数的地址赋给了函数指针func_ptr后,就可以使用func_ptr来调用add函数,和直接使用add(3,5)是等效的
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main()
{
int result;
int (*func_ptr)(int, int); // 定义函数指针
func_ptr = add; // 将函数的地址赋给函数指针
result = func_ptr(3, 5); // 使用函数指针调用函数
printf("%d\n", result); // 输出结果:8
return 0;
}
前面的基础知识讲完了,现在正式开始使用索引法实现多级菜单的第一步,定义多级菜单的数据类型,通过typedef声明的结构体设计界面菜单功能的数据类型,当前索引序号、三个按键、当前执行的函数指针,索引序号表示界面页码,要进入界面就要输入它的索引序号(就像是在宾馆房间编号,要进入房间就要知道它的房间编号),通过按键赋值索引序号达到跳转的目的(给房间编号的前台服务员),最后是执行的函数指针指向要执行的函数(可以理解为通往房间的路径)
typedef struct
{
uint8_t CurrentNum; //当前索引序号:页码
uint8_t Enter; //确认键
uint8_t Next; //下一个
uint8_t Return; //返回键
void (*Current_Operation)(void);//当前操作(函数指针)
}Menu_table_t;
根据上面自定义的数据类型,定义一个一维数组taskTable[ ],一维数组中的元素的数据类型就是Menu_table_t(就像我定义一个int array[] = { 0 ,},0的数据类型就是int),taskTable的元素中的元素与自定义数据类型里的数据类型统一对应,就像最后一个函数指针元素指向菜单界面函数,简单说明一下,taskTable[0] = {0, 1, 0, 0, Menu_Innterface},
//界面调度表 //假定 int array[] = { 0, };
Menu_table_t taskTable[] =
{
//菜单界面函数 -- 一级界面
{0, 1, 2, 3, Menu_Interface},
};
/**
* @brief 菜单界面函数
* @param 无
* @retval 无
*/
void Menu_Interface(void)
{
char buff[50];
RTC_Get_StdTime(RTC_GetCounter());
sprintf(buff,"%0.2d:%0.2d:%0.2d",RTC_CLOCK.hour, RTC_CLOCK.min, RTC_CLOCK.sec);
OLED_ShowString(4, 10, 24, buff);
sprintf(buff,"Date:%0.4d-%0.2d-%0.2d",RTC_CLOCK.year,RTC_CLOCK.mon,RTC_CLOCK.day);
OLED_ShowString(1, 6, 16, buff);
}
所以,数据类型就是这样对应的,第一个为索引序号,中间三个为按键,最后一个执行函数,将它的首地址赋值给指针函数

通过上述的对应关系,可以制定一张菜单逻辑顺序,通过中间的三个按键将索引序号赋值给任务调度序号,当在一级界面时,按下确认键就通过索引序号把务调度序号切换为1,进入到二级界面的第一个元素了,按下下一位键和返回键
uint8_t taskIndex = 0; //任务调度序号
//任务调度表
Menu_table_t taskTable[] =
{
//菜单界面函数 -- 一级界面
{0, 1, 0, 0, Menu_Interface},
//功能界面函数 -- 二级界面
{1, 4, 2, 0, Function_Interface1},
{2, 5, 3, 0, Function_Interface2},
{3, 6, 1, 0, Function_Interface3},
//功能设置界面函数 -- 三级界面
{4, 4, 4, 1, Function_Interface4},
{5, 5, 5, 2, Function_Interface5},
{6, 6, 6, 3, Function_Interface6},
};
