用C语言写一个耦合性低、完全可移植的轻量级菜单框架

2024-01-26 19:28

本文主要是介绍用C语言写一个耦合性低、完全可移植的轻量级菜单框架,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、前言

作为嵌入式软件开发,可能经常会使用命令行或者显示屏等设备实现人机交互的功能,功能中通常情况都包含 UI 菜单设计;对于复杂的UI设计,可能最多优先考虑的是使用开源的GUI库。

但GUI使用起来复杂,在简单的UI设计中则臃肿或者较难实现(比如OLED这种);基于这种情况,很多开发人员都会有自己的菜单框架模块,避免重复造轮子,网上有很多这种菜单框架的代码,但是大多耦合性太强。

代码层面上大部分都耦合了按键和不同平台(不同尺寸的OLED)等模块;并无法独立出来适配不同的菜单设计。

而多级菜单的设计也使得上层软件被迫耦合,比如一张表包含了多级菜单内容等。

基于以上种种痛点,本文介绍一个耦合性低、完全可移植的轻量级菜单框架,菜单显示风格和显示平台完全由自己根据需求设计,而菜单操作统一由菜单框架处理即可,提高程序的移植性。

二、特点

主要特点就是耦合性低,移植无需修改。且不和任何模块耦合,同时对于上层软件设计,也可以做到解耦实现。

可以为不同菜单设计不同的显示风格。

三、介绍

1、多级菜单

同级菜单以数组的方式体现,父菜单和子菜单的关联则使用链表实现。

数组元素内容有:

  • 菜单选项字符串描述(多语种可设置)

  • 菜单选项进入回调函数:当前菜单选项进入时(从父菜单进入)需要执行一次的函数

  • 菜单选项退出回调函数:当前菜单选项进入后退出时(退出至父菜单)需要执行一次的函数

  • 菜单选项重加载回调函数:当前菜单选项每次加载时(从父菜单进入或子菜单退出)需要执行一次的函数

  • 菜单选项周期调度回调函数:当前菜单选项的周期调度函数

  • 菜单选项的扩展数据

链表内存可以选择采用动态内存分配或者数组实现

方便对不同菜单界面功能解耦:

大部分菜单采用的都是数组中包含了所有不同级别的菜单选项内容实现,无法做到很好的解耦方式;

该模块通过动态绑定子菜单和链表的方式可以达到较好的解耦状态

2、显示效果

该框架只负责菜单选项控制操作,不负责在图像界面显示效果,需要在对应的回调函数中实现菜单显示效果。

设置对应的效果显示函数,即可为不同的菜单设置不同的菜单显示效果,比如图标式、列表式或右侧弹窗式等。

可以在不同显示平台体现,比如LCD、OLED或终端界面等。

3、可扩展

每级菜单选项都可以设置自定义数据,用来实现更多的菜单操作或者显示效果等。

不同级别的菜单可以设置自定义数据(比如菜单选项隐藏/图标数据等)。

4、可配置

配置选项描述
_COT_MENU_USE_MALLOC_定义则采用 malloc/free 的方式实现多级菜单, 否则通过数组的形式
_COT_MENU_USE_SHORTCUT_定义则启用快捷菜单选项进入功能
COT_MENU_MAX_DEPTH多级菜单深度
COT_MENU_MAX_NUM菜单支持的最大选项数目
COT_MENU_SUPPORT_LANGUAGE菜单支持的语种数目

5、功能多样化

多语种。

  • 支持菜单选项多语种切换,至少设置一种语言;

  • 多语种除了该方式,还可以使用多语种配置数据实现,比如键值对,键作为菜单选项字符串体现。

支持快速进入指定菜单界面。

  • 可以通过相对选项索引或者绝对选项索引路径实现。

可以实现有限界面内显示少量的菜单选项内容。

  • 有现成的函数可用,无需担心使用不同尺寸重新实现菜单选项部分可见。

四、使用说明

1、菜单初始化和使用

// 定义菜单信息,函数由主菜单模块定义并提供static cotMainMenuCfg_t sg_tMainMenu = {{"主菜单", "Main Menu"}, Hmi_EnterMainHmi, NULL, NULL, NULL};
int main(void){    cotMenu_Init(&sg_tMainMenu);
    while (1)    {        ...
        if (timeFlag)        {            timeFlag = 0;            cotMenu_Task(); // 周期调度        }    }}
 

2、主菜单定义和绑定

定义一个主菜单选项内容、主菜单显示效果函数和主菜单进入函数等。

// 扩展数据为图标文件名字cotMenuList_t sg_MainMenuTable[] = {    {{"音乐", "Music"},  Hmi_MusicEnter, Hmi_MusicExit, Hmi_MusicLoad, Hmi_MusicTask, "music"},    {{"视频", "Video"},  NULL, Hmi_VideoExit, Hmi_VideoLoad, Hmi_VideoTask, "video"},    {{"摄像机", "Camera"},  Hmi_CameraEnter, Hmi_CameraExit, Hmi_CameraLoad, Hmi_CameraTask, "camera"},    {{"设置", "Setting"}, Hmi_SetEnter, Hmi_SetExit, Hmi_SetLoad,   Hmi_SetTask, "setting"},};
/* 主菜单显示效果 */static void ShowMainMenu(cotMenuShow_t *ptShowInfo){    char *pszSelectDesc = ptShowInfo->pszItemsDesc[ptShowInfo->selectItem];    oledsize_t idx = (128 - 6 * strlen(pszSelectDesc)) / 2;
    cotOled_DrawGraphic(40, 0, (const char *)ptShowInfo->pItemsExData[ptShowInfo->selectItem], 1);
    cotOled_SetText(0, 50, "                ", 0, FONT_12X12);    cotOled_SetText(idx, 50, pszSelectDesc, 0, FONT_12X12);}
void Hmi_EnterMainHmi(void){    cotMenu_Bind(sg_MainMenuTable, COT_GET_MENU_NUM(sg_MainMenuTable), ShowMainMenu);}

3、子菜单定义和绑定

如果菜单选项有子菜单,则该菜单选项调用 cotMenu_Enter,进入回调函数不能为NULL,且该回调函数需调用 cotMenu_Bind进行绑定。

/* 设置的子菜单内容 */cotMenuList_t sg_SetMenuTable[] = {    {{"语言", "Language"},   NULL, NULL, NULL, OnLanguageFunction, NULL},    {{"蓝牙", "Bluetooth"},  NULL, NULL, NULL, OnBluetoothFunction, NULL},    {{"电池", "Battery"},    NULL, NULL, NULL, OnBatteryFunction, NULL},    {{"储存", "Store"},      NULL, NULL, NULL, OnStorageFunction, NULL},    {{"更多", "More"},       Hmi_MoreSetEnter, Hmi_MoreSetExit, Hmi_MoreSetLoad, Hmi_MoreSetTask, NULL},};
/* 设置菜单显示效果 */static void ShowSetMenu(cotMenuShow_t *ptShowInfo){    uint8_t showNum = 3;    menusize_t  tmpselect;
    cotMenu_LimitShowListNum(ptShowInfo, &showNum);
    printf("\e[0;30;46m ------------- %s ------------- \e[0m\n", ptShowInfo->pszDesc);
    for (int i = 0; i < showNum; i++)    {        tmpselect = i + ptShowInfo->showBaseItem;
        if (tmpselect == ptShowInfo->selectItem)        {            printf("\e[0;30;47m %d. %-16s\e[0m |\n", tmpselect + 1, ptShowInfo->pszItemsDesc[tmpselect]);        }        else        {            printf("\e[7;30;47m %d. %-16s\e[0m |\n", tmpselect + 1, ptShowInfo->pszItemsDesc[tmpselect]);        }    }}
void Hmi_SetEnter(void){    // 进入设置选项后绑定子菜单,同时为当前绑定的菜单设置显示效果函数    cotMenu_Bind(sg_SetMenuTable, COT_GET_MENU_NUM(sg_SetMenuTable), ShowSetMenu); }

4、菜单控制

通过调用相关函数实现菜单选项选择、进入、退出等。

​​​​​​​

// 需要先进入主菜单cotMenu_MainEnter();
// 选择上一个,支持循环选择(即第一个可跳转到最后一个)cotMenu_SelectPrevious(true);
// 选择下一个,不支持循环选择(即最后一个不可跳转到第一个)cotMenu_SelectNext(false);
// 进入,会执行菜单选项的 pfnEnterCallFun 回调函数cotMenu_Enter();
// 退出,会执行父菜单该选项的 pfnExitCallFun 回调函数,并在退出后父菜单选项列表复位从头选择cotMenu_Exit(true);

五、Demo显示效果

示例代码采用的平台是命令行输出输入显示效果:

demo中提供了如何实现图形菜单(主菜单有点粗糙)、普通列表菜单、右侧弹窗菜单(更多设置)等效果演示,菜单样式可自由扩展,足够自由;快捷菜单操作、中英文切换演示。(windows中编译需要将 demo.c转 GBK 编码,Linux 转 utf8 编码,不然可能出现汉字乱码的问题)。

图片

以下是通过单片机驱动 OLED 显示的菜单界面显示效果:

图片

六、下载链接

下载链接(点击阅读原文),或更新内容可看:

https://gitee.com/cot_package/cot_menu

这篇关于用C语言写一个耦合性低、完全可移植的轻量级菜单框架的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/647820

相关文章

禁止平板,iPad长按弹出默认菜单事件

通过监控按下抬起时间差来禁止弹出事件,把以下代码写在要禁止的页面的页面加载事件里面即可     var date;document.addEventListener('touchstart', event => {date = new Date().getTime();});document.addEventListener('touchend', event => {if (new

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte

HDU 2159 二维完全背包

FATE 最近xhd正在玩一款叫做FATE的游戏,为了得到极品装备,xhd在不停的杀怪做任务。久而久之xhd开始对杀怪产生的厌恶感,但又不得不通过杀怪来升完这最后一级。现在的问题是,xhd升掉最后一级还需n的经验值,xhd还留有m的忍耐度,每杀一个怪xhd会得到相应的经验,并减掉相应的忍耐度。当忍耐度降到0或者0以下时,xhd就不会玩这游戏。xhd还说了他最多只杀s只怪。请问他能

zoj 1721 判断2条线段(完全)相交

给出起点,终点,与一些障碍线段。 求起点到终点的最短路。 枚举2点的距离,然后最短路。 2点可达条件:没有线段与这2点所构成的线段(完全)相交。 const double eps = 1e-8 ;double add(double x , double y){if(fabs(x+y) < eps*(fabs(x) + fabs(y))) return 0 ;return x + y ;

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

数据治理框架-ISO数据治理标准

引言 "数据治理"并不是一个新的概念,国内外有很多组织专注于数据治理理论和实践的研究。目前国际上,主要的数据治理框架有ISO数据治理标准、GDI数据治理框架、DAMA数据治理管理框架等。 ISO数据治理标准 改标准阐述了数据治理的标准、基本原则和数据治理模型,是一套完整的数据治理方法论。 ISO/IEC 38505标准的数据治理方法论的核心内容如下: 数据治理的目标:促进组织高效、合理地

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return

MiniGPT-3D, 首个高效的3D点云大语言模型,仅需一张RTX3090显卡,训练一天时间,已开源

项目主页:https://tangyuan96.github.io/minigpt_3d_project_page/ 代码:https://github.com/TangYuan96/MiniGPT-3D 论文:https://arxiv.org/pdf/2405.01413 MiniGPT-3D在多个任务上取得了SoTA,被ACM MM2024接收,只拥有47.8M的可训练参数,在一张RTX