【应用笔记】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" // menustatic 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_MainEnter
或cotMenu_Enter
中调用; - 退出回调
pfnExitCallFun
, 就是在cotMenu_MainExit
或cotMenu_Exit
中调用; - 加载回调
pfnLoadCallFun
, 就是在cotMenu_Task
轮询内的开头调用, 且只调用一次; - 轮询回调
pfnRunCallFun
, 就是在cotMenu_Task
轮询内的结尾调用, 且每次都会调用; - 可以进入上面几个函数中查看具体实现了什么内容.这里不啰嗦了.
- 还有一个需要额外注册的函数,也是最重要的显示菜单函数,
- 显示回调,
pfnShowMenuFun
,在cotMenu_Task
轮询内的中间调用, 且每次都会调用; - 如果不额外赋值的话,就会沿用上一个显示回调, 在
cotMenu_MainEnter
或cotMenu_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
. - 这会有个问题, 是先显示完,然后再等待输入,并处理. 按照常规使用,更多的应该是先等待输入,然后根据输入处理,最后输出.
- 为此我拆分了
pfnRunCallFun
在cotMenu_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;
}