【应用笔记】Cot Menu 轻量级多级菜单控制框架程序(C语言)

news2024/11/17 11:27:00

【应用笔记】Cot Menu 轻量级多级菜单控制框架程序(C语言)

前言: 工作需要, 实现一个串口打印的类shell菜单. 如果按照以往的习惯我会自己重新"构思"(狗屎)一个菜单框架.之前用oled和lcd时,我都从零重复造轮子.
作为一个成熟的程序员, 应该要学会使用前人大佬总结的框架库,既能学习也能省时间.
经过ai搜索,最终找到一个今年年初(2024年1月)刚发布的轻量级框架.经过一方学习后感觉很不错,故此写下笔记.

一、准备工作

  • 可以使用vc++6.0测试,也可以使用任意单片机测试,写好的代码是基本通用的.

Vc++安装包_Visual C++ 6.0中文版安装包下载及win11安装教程
https://blog.csdn.net/weixin_46274254/article/details/123255025

  • 推荐在线思维导图工具和在线ai搜索工具;

在线思维导图
https://www.jyshare.com/more/kitymind/index.html
智谱清言
https://chatglm.cn/main/alltoolsdetail?lang=zh

  • 下载源码,作者仓库里还有其他轻量级库.

gitee仓库 cot软件包/cotMenu
https://gitee.com/cot_package/cot_menu
轻量级多级菜单控制框架程序(C语言)
https://blog.csdn.net/qq_24130227/article/details/121167276
用C语言写一个耦合性低、完全可移植的轻量级菜单框架
https://blog.csdn.net/2401_82584055/article/details/139414585

二、介绍

  • cotmenu库下载后打开,里面只有一个例程examples和库的本体cot_menu.c, cot_menu.h.
  • 本体只有2个文件,总共不超过1000行代码. 极简-轻量级.
  • 本身只实现了对菜单的控制,比如多级菜单的进入和退出,不包含菜单按键的输入和处理,还有菜单界面的定义和输出等等.
  • 所以它既可以使用按键,串口,窗口作为输入,也可以使用屏幕,串口,窗口作为输出.这部分代码需要自己实现,库中只提供了一个指针.
  • 文件夹里包含一个examples文件夹,是在电脑端运行的例程.大致演示了效果.

三、实践

  • 第一步.先创建一个工程,然后添加库,编译运行,没有报错.
    在这里插入图片描述
  • 唯一需要注意的就是那几个c语言通用库;
// #include "stdint.h" // 这个库是为了定义数据类型,如果找不到可以直接替换成下面这些
typedef signed char int8_t;
typedef short int int16_t;
typedef int int32_t;

typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;

// #include <stdbool.h> // 这个库是为了定义bool,如果找不到可以直接使用枚举自己定义.
typedef enum Bool{
	false = 0,
	true = 1
}bool;

// 其他遇到再说...

1.规划多级菜单

  • 建议使用图表或是思维导图,随意罗列一下.假设我需要实现以下菜单结构.
    在这里插入图片描述

2.准备文本

  • 无论是使用串口打印,还是屏幕显示,都需要先准备好文本,条件允许还需要中英文双语可以选择.例程cot_menu-master\examples\language中就示例.其中使用到了枚举+数组的指定索引初始化的方法,我还是第一次见,我一直以为数组是不支持乱序初始化,没想到能采用这种语法.
  • 有点小尴尬的是在vc6.0中编译报错,应该不支持这种语法.在iar的单片机工程中倒是正常编译的.
  • 注意到我把英文放在0位,中文放在1位,是为了能代码对齐,所以把中文字样放一行的最后.
// A_language.c 文件内容如下
#include "A_language.h"

// 局部变量 语言类型
static SystemLanguage_e sg_eSystemLanguage = SYSTEM_LANGUAGE_ENGLISH;

// 局部数组 语言文本
const char *(sg_kSystemLanguage[TEXT_ALL])[SYSTEM_LANGUAGE_ALL] =
{
    /*[TEXT_MENU]     = */{"Main menu", "主菜单"},
        
    /*[TEXT_MENU1A]   = */{"L1A menu", "一级菜单A"},
    /*[TEXT_MENU1B]   = */{"L1B menu", "一级菜单B"},
    /*[TEXT_MENU1C]   = */{"L1C menu", "一级菜单C"},
    /*[TEXT_MENU1D]   = */{"L1D menu", "一级菜单D"},
    
    /*[TEXT_MENU2A]   = */{"L2A menu", "二级菜单A"},
    /*[TEXT_MENU2B]   = */{"L2B menu", "二级菜单B"},
    /*[TEXT_MENU2C]   = */{"L2C menu", "二级菜单C"},
    /*[TEXT_MENU2D]   = */{"L2D menu", "二级菜单D"},
    
    /*[TEXT_MENU3A]   = */{"L3A menu", "三级菜单A"},
    /*[TEXT_MENU3B]   = */{"L3B menu", "三级菜单B"},
    
    /*[TEXT_SELECT_OPTION]    = */{"select option",     "选择操作"},
    /*[TEXT_ENTER]            = */{"enter",             "进入"},
    /*[TEXT_EXIT]             = */{"exit",              "退出"},
    /*[TEXT_NEXT]             = */{"next",              "下一个"},
    /*[TEXT_PREVIOUS]         = */{"previous",          "上一个"},
};

// 设置当期语言类型
void set_language(SystemLanguage_e lang)
{
    if (lang >= 0 && lang < SYSTEM_LANGUAGE_ALL)
    {
        sg_eSystemLanguage = lang;
    }
}

// 获取当前语言类型
const char *get_text_by_language(SystemLanguage_e lang, TextId_e id)
{
    static const char *pszNullString = "N/A";

    if (id >= 0 && id < TEXT_ALL)
    {
        return sg_kSystemLanguage[id][lang];
    }

    return pszNullString; // 未找到对应的文本
}

// 获取文本
const char *get_text(TextId_e id)
{
    static const char *pszNullString = "N/A";

    if (id >= 0 && id < TEXT_ALL)
    {
        return sg_kSystemLanguage[id][sg_eSystemLanguage];
    }

    return pszNullString; // 未找到对应的文本
}

// A_language.h 文件内容如下
#ifndef LANGUAGE_H
#define LANGUAGE_H

// 声明全局枚举
typedef enum
{
    SYSTEM_LANGUAGE_ENGLISH = 0,
    SYSTEM_LANGUAGE_CHINESE,

    SYSTEM_LANGUAGE_ALL,
} SystemLanguage_e;

typedef enum
{
    TEXT_MENU = 0,
        
    TEXT_MENU1A,
    TEXT_MENU1B,
    TEXT_MENU1C,
    TEXT_MENU1D,
    
    TEXT_MENU2A,
    TEXT_MENU2B,
    TEXT_MENU2C,
    TEXT_MENU2D,
    
    TEXT_MENU3A,
    TEXT_MENU3B,
    
    TEXT_SELECT_OPTION,
    TEXT_ENTER,
    TEXT_EXIT,
    TEXT_NEXT,
    TEXT_PREVIOUS,

    TEXT_ALL,
} TextId_e;

// 声明全局函数
extern void set_language(SystemLanguage_e lang);    // 设置当期语言类型
extern const char *get_text(TextId_e id);           // 获取文本
extern const char *get_text_by_language(SystemLanguage_e lang, TextId_e id); // 获取当前语言类型

#endif

  • 然后编译没有问题
    在这里插入图片描述

3.分析框架

  • 打开cotmenu.h,开头是c语言头文件导入,略过.然后是宏定义配置项. 只有4个,原本注释也得明明白白了.可以右键搜索哪里使用.是数组长度的定义和函数的定义.
/******************************************* 配置项 ********************************************************************/

/* 定义 _COT_MENU_USE_MALLOC_ 则采用 malloc/free 的方式实现多级菜单, 否则通过数组的形式 */
// #define _COT_MENU_USE_MALLOC_

/* 定义 _COT_MENU_USE_SHORTCUT_ 则启用快捷菜单选项进入功能 */
#define _COT_MENU_USE_SHORTCUT_

/* 多级菜单深度 */
#define COT_MENU_MAX_DEPTH              10

/* 菜单支持的最大选项数目 */
#define COT_MENU_MAX_NUM                20

/******************************************* 配置项 ********************************************************************/
  • 然后直接跳到最后,看函数的定义. 我大致进行如下分类.

在这里插入图片描述

/* Exported functions ------------------------------------------------------------------------------------------------*/

/* 菜单初始化和反初始化 */

extern int cotMenu_Init(cotMainMenuCfg_t *pMainMenu);
extern int cotMenu_DeInit(void);

extern int cotMenu_Bind(cotMenuList_t *pMenuList, menusize_t menuNum, cotShowMenuCallFun_f pfnShowMenuFun);

/* 菜单选项显示时需要使用的功能扩展函数 */

extern int cotMenu_LimitShowListNum(cotMenuShow_t *ptMenuShow, menusize_t *pShowNum);
extern int cotMenu_QueryParentMenu(cotMenuShow_t *ptMenuShow, uint8_t level);

/* 菜单操作 */

extern int cotMenu_MainEnter(void);
extern int cotMenu_MainExit(void);

extern int cotMenu_Reset(void);
extern int cotMenu_Enter(void);
extern int cotMenu_Exit(bool isReset);
extern int cotMenu_SelectPrevious(bool isAllowRoll);
extern int cotMenu_SelectNext(bool isAllowRoll);
extern int cotMenu_Select(menusize_t selectItem);

extern int cotMenu_ShortcutEnter(bool isAbsolute, uint8_t deep, ...);

/* 菜单轮询处理任务 */

extern int cotMenu_Task(void);
  • 举个简单例子, 一开始使用框架先需要列出如下内容.
  • 要使用框架就先初始化, 然后进入菜单, 就可以使用菜单, 不需要时退出, 去初始化, 一条龙标准化流程.
#include <stdio.h> // printf

#include "A_language.h" // txt
#include "cot_menu.h" // menu

static cotMainMenuCfg_t sg_tMainMenu = {NULL, NULL, NULL, NULL, NULL}; // 主菜单对象

int main (void)
{
	printf("你好 世界\n");

    cotMenu_Init(&sg_tMainMenu);    // 初始化

    cotMenu_MainEnter();            // 进入

    cotMenu_Task();     			// 使用

    cotMenu_MainExit();             // 退出

    cotMenu_DeInit();               // 去除初始化
	
    return 0;
}

4.菜单对象

  • 注意到初始化时需要一个菜单对象(结构体)cotMainMenuCfg_t / cotMenuList_t.定义了2个名字,代表主菜单对象其实和子菜单对象是一样的内容格式.
  • 第一个uMenuDesc一般是文本,就是上面定义的中英文, 使用了共用体cotMenuDsecStr_u,具体存数和取数都是自己定义.
  • 最后一个pExtendData一般是一个自定义结构体指针,用来传递所有想传递的东西.也是具体存数和取数都是自己定义.
  • 中间的几个都回调函数,在不同情况下会调用.
  • 进入回调 pfnEnterCallFun, 就是在 cotMenu_MainEntercotMenu_Enter中调用;
  • 退出回调 pfnExitCallFun, 就是在 cotMenu_MainExitcotMenu_Exit中调用;
  • 加载回调 pfnLoadCallFun, 就是在cotMenu_Task轮询内的开头调用, 且只调用一次;
  • 轮询回调 pfnRunCallFun, 就是在cotMenu_Task轮询内的结尾调用, 且每次都会调用;
  • 可以进入上面几个函数中查看具体实现了什么内容.这里不啰嗦了.
  • 还有一个需要额外注册的函数,也是最重要的显示菜单函数,
  • 显示回调,pfnShowMenuFun,在cotMenu_Task轮询内的中间调用, 且每次都会调用;
  • 如果不额外赋值的话,就会沿用上一个显示回调, 在cotMenu_MainEntercotMenu_Enter中实现.
/**
  * @brief 菜单信息注册结构体
  * 
  */
typedef struct
{
    cotMenuDsecStr_u     uMenuDesc;        /*!< 当前菜单的描述 */

	    cotMenuCallFun_f     pfnEnterCallFun;  /*!< 当前菜单选项进入时(从父菜单进入)需要执行一次的函数, 为NULL不执行 */

    cotMenuCallFun_f     pfnExitCallFun;   /*!< 当前菜单选项进入后退出时(退出至父菜单)需要执行一次的函数, 为NULL不执行 */
    
    cotMenuCallFun_f     pfnLoadCallFun;   /*!< 当前菜单选项每次加载时(从父菜单进入或子菜单退出)需要执行一次的函数, 为NULL不执行 */

    cotMenuCallFun_f     pfnRunCallFun;    /*!< 当前菜单选项的周期调度函数 */

    void                *pExtendData;      /*!< 当前选项的菜单显示效果函数扩展数据入参, 可自行设置该内容 */
} cotMenuList_t, cotMainMenuCfg_t;

5.回调函数

  • 如果有具体想执行的内容, 回调函数可以直接写空NULL,我这里为了演示都写一下.
  • 注意,注册函数cotMenu_Bind的对象我填的是0,一会再补上,现在主要是看其他回调函数的使用方法.
// 轮询时每次执行
void Show_Main(cotMenuShow_t *ptShowInfo)
{
}

// 进入时执行一次
void Enter_Main(const cotMenuItemInfo_t *pItemInfo)
{
    // cotMenu_Bind(NULL, 0, Show_Main); // 注册函数, 注册显示函数, 放着执行一次的 load 或 enter 中即可.
}

// 退出时执行一次
void Exit_Main(const cotMenuItemInfo_t *pItemInfo)
{
}

// 轮询前执行一次
void Load_Main(const cotMenuItemInfo_t *pItemInfo)
{
    cotMenu_Bind(NULL, 0, Show_Main); // 注册函数, 注册显示函数, 放着执行一次的 load 或 enter 中即可.
}

// 轮询时每次执行
void Run_Main(const cotMenuItemInfo_t *pItemInfo)
{
}

// 主菜单对象
static cotMainMenuCfg_t sg_tMainMenu = {(void *)TEXT_MENU, // "main menu" // 这个位置是共用体,如果存枚举,取数时就按枚举取,如果存字符串指针,取数时就按字符串指针取数
                                        Enter_Main, //
                                        Exit_Main, 
                                        Load_Main, 
                                        Run_Main}; 

6.轮询函数

  • 打开ccot_menu.c,拉到最后看cotMenu_Task()函数.
  • 可以看到其实主要是就是调用了3个函数.
  • 加载回调pfnLoadCallFun, 显示回调pfnShowMenuFun, 轮询回调pfnRunCallFun.
  • 其中显示回调会先拷贝子菜单列表pMenuList,主要就是用来打印显示的.
  • 而这个显示回调pfnShowMenuFun是需要通过调用注册函数cotMenu_Bind进行修改赋值.
int cotMenu_Task(void)
{
    int i;
    cotMenuList_t *pMenuList;
    cotMenuShow_t tMenuShow;

    if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.isEnterMainMenu == 0)
    {
        return -1;
    }

    if (sg_tMenuManage.pfnLoadCallFun != NULL)
    {
        cotMenuItemInfo_t tItemInfo;

        tItemInfo.uMenuDesc = sg_tMenuManage.pMenuCtrl->uMenuDesc;
        tItemInfo.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;
        sg_tMenuManage.pfnLoadCallFun(&tItemInfo);
        sg_tMenuManage.pfnLoadCallFun = NULL;
    }
    
    if (sg_tMenuManage.pMenuCtrl->pMenuList != NULL)
    {
        pMenuList = sg_tMenuManage.pMenuCtrl->pMenuList;
        tMenuShow.itemsNum = sg_tMenuManage.pMenuCtrl->itemsNum;
        tMenuShow.selectItem = sg_tMenuManage.pMenuCtrl->selectItem;
        tMenuShow.showBaseItem = sg_tMenuManage.pMenuCtrl->showBaseItem;

        tMenuShow.uMenuDesc = sg_tMenuManage.pMenuCtrl->uMenuDesc;
        tMenuShow.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;

        for (i = 0; i < tMenuShow.itemsNum && i < COT_MENU_MAX_NUM; i++)
        {
            tMenuShow.uItemsListDesc[i] = pMenuList[i].uMenuDesc;
            tMenuShow.pItemsListExtendData[i] = pMenuList[i].pExtendData;
        }

        if (sg_tMenuManage.pMenuCtrl->pfnShowMenuFun != NULL)
        {
            sg_tMenuManage.pMenuCtrl->pfnShowMenuFun(&tMenuShow);
        }

        sg_tMenuManage.pMenuCtrl->showBaseItem = tMenuShow.showBaseItem;
    }

    if (sg_tMenuManage.pMenuCtrl->pfnRunCallFun != NULL)
    {
        cotMenuItemInfo_t tItemInfo;

        tItemInfo.uMenuDesc = sg_tMenuManage.pMenuCtrl->uMenuDesc;
        tItemInfo.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;
        sg_tMenuManage.pMenuCtrl->pfnRunCallFun(&tItemInfo);
    }

7.注册函数

  • 主菜单的内容基本就这些,接下来就是子菜单相关的内容.使用注册函数写入子菜单数据.
  • 子菜单的格式和主菜单的格式是一样的.就不赘述.
  • 总结,注册函数cotMenu_Bind就是写入子菜单数组sg_Menu1_Table, 总长度COT_GET_MENU_NUM(sg_Menu1_Table), 和显示回调函数Show_Main.
/* 一级菜单 */
cotMenuList_t sg_Menu1_Table[] = 
{
    COT_MENU_ITEM_BIND(TEXT_MENU1A,  NULL, NULL, NULL, NULL, NULL),
    COT_MENU_ITEM_BIND(TEXT_MENU1B,  NULL, NULL, NULL, NULL, NULL),
    COT_MENU_ITEM_BIND(TEXT_MENU1C,  NULL, NULL, NULL, NULL, NULL),
    COT_MENU_ITEM_BIND(TEXT_MENU1D,  NULL, NULL, NULL, NULL, NULL),
    /* 坑爹的vc6.0可能不支持指定结构体元素初始化,所以可以改成下面的写法.    
    {(void *)TEXT_MENU1A, NULL, NULL, NULL, NULL, NULL},
    {(void *)TEXT_MENU1B, NULL, NULL, NULL, NULL, NULL},
    {(void *)TEXT_MENU1C, NULL, NULL, NULL, NULL, NULL},
    {(void *)TEXT_MENU1D, NULL, NULL, NULL, NULL, NULL},
    */
};
// 轮询前执行一次
void Load_Main(const cotMenuItemInfo_t *pItemInfo)
{
    cotMenu_Bind(sg_Menu1_Table, COT_GET_MENU_NUM(sg_Menu1_Table), Show_Main); // 注册函数, 注册显示函数, 放着执行一次的 load 或 enter 中即可.
}

8.显示回调

  • 如果这里面写的LCD程序,那效果就是显示器菜单,如果写的串口,就是shell菜单.
  • 而且想显示横版还是竖版,亦或是图片都可以自己实现.
  • 下面是个简易的轮询显示.
// 轮询时每次执行
void Show_Main(cotMenuShow_t *ptShowInfo)
{
    uint8_t showNum = 3; // 显示最大行数
    cotMenu_LimitShowListNum(ptShowInfo, &showNum); // 根据设定截取菜单内容

    printf("\n ------------- %s ------------- \n", get_text((TextId_e)ptShowInfo->uMenuDesc.textId)); // 显示总标题

    for (int i = 0; i < showNum; i++)   // 轮询显示菜单
    {
        menusize_t tmpselect = i + ptShowInfo->showBaseItem; // 计算当前实际行数

        printf(" %-10s %c \n",                                      // 显示内容
            get_text((TextId_e)ptShowInfo->uItemsListDesc[tmpselect].textId), // 根据枚举索引取出字符串
            (tmpselect == ptShowInfo->selectItem) ? ('<') : (' ')); // 箭头
    }

    printf("\n"); // 
}
  • 最后如果没有出差错,点击运行就能看到初步效果了.
  • 如果有条件可以进行在线调试,逐步卡断点,看看代码运行顺序和逻辑,因为赋值指针很多,会晕.

在这里插入图片描述

// main.c 文件
#include <stdio.h> // printf

#include "A_language.h" // txt
#include "cot_menu.h" // menu

// 轮询时每次执行
void Show_Main(cotMenuShow_t *ptShowInfo)
{
    uint8_t showNum = 3; // 显示最大行数
    cotMenu_LimitShowListNum(ptShowInfo, &showNum); // 根据设定截取菜单内容

    printf("\n ------------- %s ------------- \n", get_text((TextId_e)ptShowInfo->uMenuDesc.textId)); // 显示总标题

    for (int i = 0; i < showNum; i++)   // 轮询显示菜单
    {
        menusize_t tmpselect = i + ptShowInfo->showBaseItem; // 计算当前实际行数

        printf(" %-10s %c \n",                                      // 显示内容
            get_text((TextId_e)ptShowInfo->uItemsListDesc[tmpselect].textId), // 根据枚举索引取出字符串
            (tmpselect == ptShowInfo->selectItem) ? ('<') : (' ')); // 箭头
    }

    printf("\n"); // 
}

// 进入时执行一次
void Enter_Main(const cotMenuItemInfo_t *pItemInfo)
{
    // cotMenu_Bind(NULL, 0, Show_Main); // 注册函数, 注册显示函数, 放着执行一次的 load 或 enter 中即可.
}

// 退出时执行一次
void Exit_Main(const cotMenuItemInfo_t *pItemInfo)
{
}

/* 一级菜单 */
static cotMenuList_t sg_Menu1_Table[] = 
{
    {(void *)TEXT_MENU1A, NULL, NULL, NULL, NULL, NULL},
    {(void *)TEXT_MENU1B, NULL, NULL, NULL, NULL, NULL},
    {(void *)TEXT_MENU1C, NULL, NULL, NULL, NULL, NULL},
    {(void *)TEXT_MENU1D, NULL, NULL, NULL, NULL, NULL},
};

// 轮询前执行一次
void Load_Main(const cotMenuItemInfo_t *pItemInfo)
{
    cotMenu_Bind(sg_Menu1_Table, COT_GET_MENU_NUM(sg_Menu1_Table), Show_Main); // 注册函数, 注册显示函数, 放着执行一次的 load 或 enter 中即可.
}

// 轮询时每次执行
void Run_Main(const cotMenuItemInfo_t *pItemInfo)
{
}

// 主菜单对象
static cotMainMenuCfg_t sg_tMainMenu = {(void *)TEXT_MENU, // "main menu" // 这个位置是共用体,如果存枚举,取数时就按枚举取,如果存字符串指针,取数时就按字符串指针取数
                                        Enter_Main, //
                                        Exit_Main, 
                                        Load_Main, 
                                        Run_Main}; 

int main (void)
{
	printf("你好 世界\n");

    cotMenu_Init(&sg_tMainMenu);    // 初始化

    cotMenu_MainEnter();            // 进入

    cotMenu_Task();                 // 使用

    cotMenu_MainExit();             // 退出

    cotMenu_DeInit();               // 去除初始化
	
    return 0;
}

9.按键控制输入

  • 剩下最后一个就是按键输入了, 例程中,将按键输入放在了轮询回调中执行pfnRunCallFun.
  • 这会有个问题, 是先显示完,然后再等待输入,并处理. 按照常规使用,更多的应该是先等待输入,然后根据输入处理,最后输出.
  • 为此我拆分了pfnRunCallFuncotMenu_Task中的调用. 拆分后就可以在前一半执行按键操作,后一般执行打印等其他操作了.
int cotMenu_Task(void)
{
	// 略...    
    if (sg_tMenuManage.pMenuCtrl->pfnRunCallFun != NULL) // 在加载回调前调用
    {
        cotMenuItemInfo_t tItemInfo;

        tItemInfo.uMenuDesc = 0; // 传入固定空指针, 或者按其他方式区分,
        tItemInfo.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;
        sg_tMenuManage.pMenuCtrl->pfnRunCallFun(&tItemInfo); 
    }

    if (sg_tMenuManage.pfnLoadCallFun != NULL)
    {
    	// 略...
    }
    
    if (sg_tMenuManage.pMenuCtrl->pMenuList != NULL)
    {
    	// 略...
    }

    if (sg_tMenuManage.pMenuCtrl->pfnRunCallFun != NULL)
    {
        cotMenuItemInfo_t tItemInfo;

        tItemInfo.uMenuDesc = sg_tMenuManage.pMenuCtrl->uMenuDesc;
        tItemInfo.pExtendData = sg_tMenuManage.pMenuCtrl->pExtendData;
        sg_tMenuManage.pMenuCtrl->pfnRunCallFun(&tItemInfo);
    }

    return 0;
}
  • 完成后就可以开始完善pfnRunCallFun的内容,加入等待按键

偷懒的我直接贴上例程的代码,

// 轮询时每次执行
void Run_Main(const cotMenuItemInfo_t *pItemInfo)
{
    int cmd;

	if (pItemInfo[0].uMenuDesc != 0) // 拆分2部分执行
	{
	    printf("%s(1-%s; 2-%s; 3-%s; 4-%s(%s): ", 
	            get_text(TEXT_SELECT_OPTION), 
	            get_text(TEXT_ENTER), get_text(TEXT_EXIT),
	            get_text(TEXT_NEXT), get_text(TEXT_PREVIOUS));
	    scanf(" %d", &cmd); // 空格作用是忽略上次的回车
    }
 	else  // 拆分2部分执行
 	{
	    switch (cmd)
	    {
	    case 1:
	        cotMenu_Enter();
	        break;
	    case 2:
	        cotMenu_Exit(false);
	        break;
	    case 3:
	        cotMenu_SelectNext(true);
	        break;
	    case 4:
	        cotMenu_SelectPrevious(true);
	        break;        
	    default:
	        break;    
	    }
 	}
}

上述代码还没写完, 剩下日后如果有兴致再补上,断断续续写了一下午,好累,明天周一还要上班.
不过如果看到这的人, 剩下的我不会应该也能自己领会了吧.

补充

1.0

  • 修改 cotMenu_Select函数,内置保护措施应该写错了.没有正确判断索引,防止越位
/**
  * @brief      选择指定的菜单选项
  *
  * @param      selectItem 指定的菜单选项
  * @return     0,成功; -1,失败
  */
int cotMenu_Select(menusize_t selectItem)
{
    if (sg_tMenuManage.pMenuCtrl == NULL || sg_tMenuManage.pMenuCtrl->pMenuList == NULL || sg_tMenuManage.isEnterMainMenu == 0)
    {
        return -1;
    }

    if (sg_tMenuManage.pMenuCtrl->selectItem >= sg_tMenuManage.pMenuCtrl->itemsNum)
    {
        return -1;
    }
    
    if (selectItem >= sg_tMenuManage.pMenuCtrl->itemsNum) // 保护措施
    {
        return -1;
    }

    sg_tMenuManage.pMenuCtrl->selectItem = selectItem; // 直接赋值

    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2133456.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

野火霸天虎V2学习记录1

文章目录 嵌入式开发常识汇总1、嵌入式Linux和stm32之间的区别和联系2、stm32程序下载方式3、Keil5安装芯片包4、芯片封装种类5、STM32命名6、数据手册和参考手册7、什么是寄存器、寄存器映射和内存映射8、芯片引脚顺序9、stm32芯片里有什么10、存储器空间的划分11、如何理解寄…

HFish开源蜜罐系统常见问题排查

HFish开源蜜罐系统常见问题排查 HFish是一款社区型免费蜜罐&#xff0c;侧重企业安全场景&#xff0c;从内网失陷检测、外网威胁感知、威胁情报生产三个场景出发&#xff0c;为用户提供可独立操作且实用的功能&#xff0c;通过安全、敏捷、可靠的中低交互蜜罐增加用户在失陷感知…

Pr:Adobe SRT

Adobe SRT&#xff08;Secure Reliable Transport&#xff09;主要用于远程传输高质量视频&#xff0c;特别是在不稳定或高延迟的网络环境中。 1、实时流媒体传输 Adobe SRT 支持实时视频传输&#xff0c;确保即使在低带宽或高延迟的网络条件下&#xff0c;仍能够可靠地发送高质…

第J1周:ResNet-50算法实战与解析

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 文章目录 一、前期工作1、ResNet-50总体结构2、设置GPU3、导入数据 二、数据预处理1、加载数据2、可视化数据3、再次检查数据4、配置数据集 三、构建ResNet-50…

建筑节能监测系统解决方案

jianzhunenghao 建筑节能监测系统公共建筑能耗监测系统建筑能耗监测系统节能监测系统能耗监测建筑能耗监测能耗分析能耗管理能耗预测能耗监控能耗监测平台建筑能耗 介绍 能耗监控系统是为耗电量、耗水量、耗气量&#xff08;天然气量或者煤气量&#xff09;、集中供热耗热量…

无人机建模详解!!!

一、无人机拍摄 1. 准备工作 了解场地信息&#xff1a;在规划航线之前&#xff0c;需要了解场地的范围、地貌、树木遮挡情况、建筑和树木高度等&#xff0c;以便为后续的规划航线提供帮助。 选择拍摄时间&#xff1a;最佳拍摄条件为光线充足且风速较小的天气的10点-16点&…

校园管理系统创新:Spring Boot框架应用案例

第4章 系统设计 4.1 系统体系结构 校园管理系统的结构图4-1所示&#xff1a; 图4-1 系统结构 登录系统结构图&#xff0c;如图4-2所示&#xff1a; 图4-2 登录结构图 校园管理系统结构图&#xff0c;如图4-3所示。 图4-3 校园管理系统结构图 4.2开发流程设计 系统流程的分…

C语言整型数据在内存中的存储(22)

文章目录 前言一、整数在内存中的存储二、大小端字节序和字节序判断什么是大小端&#xff1f;为什么会有大小端&#xff1f;练习练习1练习2练习3练习4练习5练习6练习7 总结 前言 本篇是修炼内功的文章   首先&#xff0c;你先明白一个事实&#xff0c;数据在内存中是以二进制…

预测日前电价:回顾最先进的算法、最佳实践和公开基准——阅读笔记

Forecasting day-ahead electricity prices: A review of state-of-the-art algorithms, best practices and an open-access benchmark 预测日前电价&#xff1a;回顾最先进的算法、最佳实践和公开基准 Applied Energy (2021) 摘要&#xff1a;电价预测在过去二十年间已经得到…

python画图|3D surface基础教程

画三维图在当前的需求越来越强烈&#xff0c;因此掌握3D图的画法至关重要。 让我们先来学习3D surface基础教程。 【1】官网教程 首先是来到官网&#xff0c;找到教程&#xff0c;详见下述链接&#xff1a; 3D surface (colormap) — Matplotlib 3.9.2 documentation 教程…

【Linux】代理服务器

一、正向代理 1.1 正向代理的概述 正向代理是一种常见的网络代理方式&#xff0c;他位于客户端和目标服务器之间&#xff0c;代表客户端向目标服务器发送请求。正向代理服务器接受客户端的请求&#xff0c;然后将请求转发给目标服务器&#xff0c;最后将目标服务器的响应返回给…

mybatis与concat实现模糊查询、mybatis中模糊查询concat传入参数为空时的解决方法

文章目录 在mybatis中&#xff0c;一般模糊查询like习惯用concat进行拼接&#xff0c;但是当传入的参数为空时&#xff0c;查询不出数据。 那是因为concat中&#xff0c;若传入参数为null, 则返回null。 以下整理mybatis中like模糊查询的写法 <select id"findByKeyw…

【uni-app】命令行创建 uni-app 项目

命令行创建 uni-app 项目 优势 通过命令行创建 uni-app 项目&#xff0c;不必依赖 HBuilderX&#xff0c;TypeScript 类型支持友好。 命令行创建 uni-app 项目&#xff1a; vue3 ts 版 &#x1f449;国内 gitee 下载github 下载 # 通过 git 从 gitee 克隆下载 git clone…

Servlet学习详解--基本涵盖所有Servlet知识点

目录 一、Servlet二、 Servlet入门2.1. 执行原理2.2. 实现Servlet接口重写其五个方法及其生命周期 三、Request请求对象3.1. 获取请求消息数据3.2. 获取请求头数据3.3. 获取请求体数据3.4. 设置编码3.5. 其他通用功能3.6. 请求转发(Forward)3.7. 转发共享数据 四、Response对象…

算法刷题[比较两个字符串的最大公字符串(滑动窗口实现)]

题目&#xff1a;编程实现&#xff1a;找出两个字符串中最大公共子字符串,如"abccade","dgcadde"的最大子串为"cad" 代码如下所示&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #inclu…

Wophp靶场漏洞挖掘

首先进入网站发现有个搜索框&#xff0c;那么我们试一下xss和SQL注入 SQL注入漏洞 发现这里页面没有给我们回显 那么我们尝试sql注入 查数据库 查表 最后查出账号密码 找到账号密码之后我们去找后台登录 进入后台后发现这里有个flag flag 接着往下翻找到一个文件上传的地方 …

STM32启用FPU浮点运算

这篇文章产生背景&#xff1a;其他人的文章太杂了&#xff0c;对我这种菜鸡无法接受&#xff1b; 参考文章&#xff1a; stm32h743单片机嵌入式学习笔记7-FPU_stmh743vit4-CSDN博客 stm32F407 打开 FPU(浮点运算处理器)_stm32f407开启fpu-CSDN博客 STM32F4CubeMXHal库下使能…

基于微信小程序的食堂点餐预约管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 基于微信小程序JavaSpringBootVueMySQL的食…

linux-L6 linux管理服务的启动、重启、停止、重载、查看状态命令

来重启一下某一个服务 1.使用命令查看所有的服务状态 Systemctl找到其中的相关的服务 systemctl status xxx_你的应用程序的服务__xxx3.重启该服务 systemctl restart xxx_你的应用程序的服务__xxx下面的是备用&#xff0c;需要用的时候&#xff0c;查看就好了 启动服务 …

[机器学习]聚类算法

1 聚类算法简介 # 导包 from sklearn.datasets import make_blobs import matplotlib.pyplot as plt from sklearn.cluster import KMeans from sklearn.metrics import calinski_harabasz_score # 构建数据 x,ymake_blobs(n_samples1000,n_features2,centers[[-1,-1],[0,0],[1…