打印
[文档下载]

简易多级菜单(数组查表法)

[复制链接]
1897|38
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
yeates333|  楼主 | 2025-3-20 21:21 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
u8g2, 16, ST, AMP, AWS, ic
本帖最后由 yeates333 于 2025-3-20 21:23 编辑

1 多级菜单多级菜单的实现,大体分为两种设计思路:
  • 通过双向链表实现
  • 通过数组查表实现
总体思路都是把菜单的各个界面联系起来,可以从上级菜单跳到下级菜单,也可从下级菜单返回上级菜单。
数组查表的方式比较简单,易于理解,本篇就来使用数组查表发在RVB2601上实现多级菜单的显示。
2 代码实现2.1 数组查表 首先需要定义一个结构体:

typedef struct
{
        uchar current;
        uchar up;//向上翻索引号
        uchar down;//向下翻索引号
        uchar enter;//确认索引号
        void (*current_operation)();
} key_table;




  • current:当前页面的索引号
  • up:按下“向上翻“按钮后要跳转到的页面索引号
  • down:按下“向下翻“按钮后要跳转到的页面索引号
  • enter:按下“确认“按钮后要跳转到的页面索引号
  • current_operation:当前页面的索引号要执行的显示函数,这是一个函数指针
注意:对于菜单显示的操作,用到了3个按键,分别是向下、向下和确认,如果单片机上的IO资源较为紧张,还可以把“向上翻”按钮省去,只通过“向下翻”按钮来实现循环访问,对应的结构体也可以去掉该成员。
然后定义一个表,用来定义各个页面间如何跳转

key_table table[30]=
{
        //第0层
        {0,0,0,1,(*fun_0)},
       
    //第1层
        {1,4,2, 5,(*fun_a1)},
        {2,1,3, 9,(*fun_b1)},
        {3,2,4,13,(*fun_c1)},               
        {4,3,1, 0,(*fun_d1)},
       
    //第2层
        {5,8,6,17,(*fun_a21)},                                       
        {6,5,7,18,(*fun_a22)},
        {7,6,8,19,(*fun_a23)},                                                                       
        {8,7,5, 1,(*fun_a24)},
       
        { 9,12,10,20,(*fun_b21)},                                       
        {10, 9,11,21,(*fun_b22)},
        {11,10,12,22,(*fun_b23)},                                                                       
        {12,11, 9, 2,(*fun_b24)},
       
        {13,16,14,23,(*fun_c21)},                                       
        {14,13,15,24,(*fun_c22)},                                                       
        {15,14,16,25,(*fun_c23)},                                                       
        {16,15,13, 3,(*fun_c24)},
       
    //第3层
        {17,17,17,5,(*fun_a31)},                                               
        {18,18,18,6,(*fun_a32)},                                       
        {19,19,19,7,(*fun_a33)},
       
        {20,20,20, 9,(*fun_b31)},                                                       
        {21,21,21,10,(*fun_b32)},                                               
        {22,22,22,11,(*fun_b33)},
       
        {23,23,23,13,(*fun_c31)},                                               
        {24,24,24,14,(*fun_c32)},                                               
        {25,25,25,15,(*fun_c33)},                                                               
};





这里解释一下该表是如何工作的:
  • 此表,表示了4级菜单的显示关系(注意第0层其实只是一个欢迎界面)
  • 第一层菜单,只有4个选项,因此这里只列了4行(注意最后一个选项用作返回上一级,无实际内容含义)
  • 第二层菜单,就是对第一层菜单中的3个实际的选项进行进一步的介绍,每种介绍又有4个子项(注意最后一个选项也是用作返回上一级,无实际内容含义),因此,这里的第二层菜单列了3x4=12行
  • 第三层菜单,又是对第二层菜单中的子项进行进一步的介绍(3个分类,每类有3个子项),所以第三层菜单列了9行
  • 注意数组中每一行的第1个数组,是索引号,先列举一个实际的例子进行分析:



上图就是一个实际的4级菜单要显示的内容,每个条目前,标记了索引号(0~25),即对应数组在定义的索引号。
比如数组关于第0层和第1层的定义:



//第0层
{0,0,0,1,(*fun_0)},

//第1层
{1,4,2, 5,(*fun_a1)},
{2,1,3, 9,(*fun_b1)},
{3,2,4,13,(*fun_c1)},               
{4,3,1, 0,(*fun_d1)},



  • 先看第一行:索引是0,显示欢迎界面;后面的两个0表示此时按“上翻”和“下翻”无效,继续显示欢迎界面;再后面的1表示按下“确认”按钮后,跳转到索引1处(即显示第1级目录,且指向第1级的第1个子项);最后是此索引要显示的具体内容,fun_0就是控制屏幕显示欢迎界面
  • 再看第二行:索引是1,显示第1级目录,且指向第1级的第1个子项(天气);后面的4表示此时按“上翻”跳转到索引4,即显示第1级目录,且指向第1级的第4个子项(Return);再后面的2表示此时按“下翻”跳转到索引2,即显示第1级目录,且指向第1级的第2个子项(音乐);再后面的5表示按下“确认”按钮后,跳转到索引5处(即显示第2级目录,且指向第2级的第1个子项-杭州);最后是此索引要显示的具体内容,fun_a1就是控制屏幕显示第1级目录,且指向第1级的第1个子项(天气)
  • 其它行的含义与之类似
通过分析,不难发现,这些数组在空间上的关系:



对于菜单的最底层,因为没有上翻和下翻的功能需求,因此每行的前3个数字都是当前的索引号:



//第3层
{17,17,17,5,(*fun_a31)},                                               
{18,18,18,6,(*fun_a32)},                                       
{19,19,19,7,(*fun_a33)},

{20,20,20, 9,(*fun_b31)},                                                       
{21,21,21,10,(*fun_b32)},                                               
{22,22,22,11,(*fun_b33)},

{23,23,23,13,(*fun_c31)},                                               
{24,24,24,14,(*fun_c32)},                                               
{25,25,25,15,(*fun_c33)},

       

2.2 具体的显示函数 对于函数要显示的具体内容,根据自己的实现需要显示即可。
这里我使用的是OLED屏幕,借助U8g2图形库进行内容显示,以下是部分显示示例:

/*********第1层***********/
void fun_a1()   
{       
        u8g2_DrawStr(&u8g2,0,16,">");
        u8g2_DrawStr(&u8g2,16,16,"[1]Weather");
        u8g2_DrawStr(&u8g2,16,32,"[2]Music");
        u8g2_DrawStr(&u8g2,16,48,"[3]Device Info");
        u8g2_DrawStr(&u8g2,16,64,"<--");                                                                                                                                                                       
}

void fun_b1()   
{       
        u8g2_DrawStr(&u8g2,0,32,">");
        u8g2_DrawStr(&u8g2,16,16,"[1]Weather");
        u8g2_DrawStr(&u8g2,16,32,"[2]Music");
        u8g2_DrawStr(&u8g2,16,48,"[3]Device Info");
        u8g2_DrawStr(&u8g2,16,64,"<--");                                                                                                                                                                               
}

void fun_c1()     
{       
        u8g2_DrawStr(&u8g2,0,48,">");
        u8g2_DrawStr(&u8g2,16,16,"[1]Weather");
        u8g2_DrawStr(&u8g2,16,32,"[2]Music");
        u8g2_DrawStr(&u8g2,16,48,"[3]Device Info");
        u8g2_DrawStr(&u8g2,16,64,"<--");                                                                                                                                                                       
}

void fun_d1()     
{       
        u8g2_DrawStr(&u8g2,0,64,">");
        u8g2_DrawStr(&u8g2,16,16,"[1]Weather");
        u8g2_DrawStr(&u8g2,16,32,"[2]Music");
        u8g2_DrawStr(&u8g2,16,48,"[3]Device Info");
        u8g2_DrawStr(&u8g2,16,64,"<--");                                                                                                                                                                                       
}

/*********第2层***********/
void fun_a21()     
{       
        u8g2_DrawStr(&u8g2,0,16,">");
        u8g2_DrawStr(&u8g2,16,16,"* HangZhou");
        u8g2_DrawStr(&u8g2,16,32,"* BeiJing");
        u8g2_DrawStr(&u8g2,16,48,"* ShangHai");
        u8g2_DrawStr(&u8g2,16,64,"<--");                                                                                                                                                                               
}
//省略...





2.3 按键切换页面 页面的切换,这里里简单的按钮轮询为例,比如初始显示欢迎界面的状态下,按下不同按键后,通过数组查表,确定要跳转到的索引号,然后根据索引号,通过函数指针执行索引号对应的显示函数,即实现了一次页面切换。
然后,就是在新的页面状态,收到下一个按钮指令,再切换到下一个显示状态。



void (*current_operation_index)(); //定义一个函数指针

//...
while(1)
{
    if((KEY1==0)||(KEY2==0)||(KEY3==0))
    {
        delay_ms(10);//消抖
        if(KEY1==0)
        {
            func_index = table[func_index].up;    //向上翻
            while(!KEY1);//松手检测
        }
        if(KEY2==0)
        {
            func_index = table[func_index].down;    //向下翻
            while(!KEY2);
        }
        if(KEY3==0)
        {
            func_index = table[func_index].enter;    //确认
            while(!KEY3);
        }
    }       

    if (func_index != last_index)
    {
        current_operation_index = table[func_index].current_operation;

        u8g2_ClearBuffer(&u8g2);
        (*current_operation_index)();//执行当前操作函数
        u8g2_SendBuffer(&u8g2);

        last_index = func_index;
    }
}





使用特权

评论回复
沙发
qqq_147258| | 2025-6-1 13:46 | 只看该作者
十年前吧,有个“傻孩子菜单”的程序,你俩类似。

使用特权

评论回复
板凳
caigang13| | 2025-6-2 10:07 | 只看该作者
移植一个GUI多nice的

使用特权

评论回复
地板
快乐制造机| | 2025-6-4 12:27 | 只看该作者
这个实现方法确实简单明了,对于嵌入式系统来说,数组查表是一种非常节省资源的方法。

使用特权

评论回复
5
绝影孤狼| | 2025-6-4 18:02 | 只看该作者
这个实现方法很巧妙,用数组查表的方式来管理多级菜单的跳转逻辑,大大简化了代码的复杂度。

使用特权

评论回复
6
作业粉碎机| | 2025-6-4 18:34 | 只看该作者
这个帖子提供了一个很好的多级菜单实现方案,特别是数组查表法,对于嵌入式系统来说非常实用。

使用特权

评论回复
7
timfordlare| | 2025-6-8 19:49 | 只看该作者
使用数组查表法可以有效地管理和导航多级菜单。

使用特权

评论回复
8
uytyu| | 2025-6-10 09:55 | 只看该作者
受数组大小和栈空间限制,避免过深的菜单层级

使用特权

评论回复
9
lzbf| | 2025-6-10 14:50 | 只看该作者
实际开发中需注意数组越界保护、显示优化和功能解耦,以提升代码健壮性。

使用特权

评论回复
10
uiint| | 2025-6-10 18:08 | 只看该作者
新增菜单项时,只需在数组中添加条目并绑定显示函数,无需修改逻辑代码。

使用特权

评论回复
11
maudlu| | 2025-6-10 18:40 | 只看该作者
使用结构体数组存储菜单的层级关系、跳转逻辑和显示函数。

使用特权

评论回复
12
claretttt| | 2025-6-10 21:25 | 只看该作者
灵活调用操作函数或进入子菜单。              

使用特权

评论回复
13
robertesth| | 2025-6-12 15:48 | 只看该作者
用const关键字将菜单文本存储在 Flash 中

使用特权

评论回复
14
benjaminka| | 2025-6-16 13:05 | 只看该作者
typedef struct {
    uint8_t current;    // 当前菜单项索引
    uint8_t up;         // 上翻键跳转索引
    uint8_t down;       // 下翻键跳转索引
    uint8_t enter;      // 确认键跳转索引
    void (*current_operation)(void); // 显示函数指针
} MenuTable;

// 定义菜单数组(示例为4级菜单)
#define TABLE_SIZE 30
MenuTable table[TABLE_SIZE] = {
    // 第0层(欢迎界面)
    {0, 0, 0, 1, ShowWelcome},
   
    // 第1层(主菜单)
    {1, 4, 2, 5, ShowMainMenu},
    {2, 1, 3, 9, ShowMusic},
    {3, 2, 4, 13, ShowDeviceInfo},
    {4, 3, 1, 0, ReturnToWelcome},
   
    // 第2层(子菜单)
    {5, 8, 6, 17, ShowWeatherSub},
    {6, 5, 7, 18, ShowMusicSub},
    {7, 6, 8, 19, ShowDeviceSub},
    {8, 7, 5, 1, ReturnToMain},
   
    // 第3层(功能项)
    {17, 17, 17, 5, ConnectWiFi},
    {18, 18, 18, 6, PlayMusic},
    {19, 19, 19, 7, ShowDeviceStatus},
};

使用特权

评论回复
15
kkzz| | 2025-6-16 14:49 | 只看该作者
菜单结构和字符串常量在内存中正确分配,避免溢出或未定义行为。

使用特权

评论回复
16
biechedan| | 2025-6-16 16:34 | 只看该作者
支持多层嵌套,理论上可通过增加数组容量实现更多层级。

使用特权

评论回复
17
albertaabbot| | 2025-6-16 22:40 | 只看该作者
数组查表法是一种高效且易于维护的设计方法。

使用特权

评论回复
18
janewood| | 2025-6-17 12:34 | 只看该作者
多级菜单的本质是​​树状结构​​,通过“主菜单→子菜单→功能项”的层级组织。

使用特权

评论回复
19
gygp| | 2025-6-17 13:12 | 只看该作者
通过预定义数组快速访问菜单项。              

使用特权

评论回复
20
wilhelmina2| | 2025-6-17 14:33 | 只看该作者
可以实现一个简易的多级菜单系统,适用于各种单片机应用,如嵌入式设备的用户界面、调试工具等。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

20

主题

1470

帖子

1

粉丝