说明:这是一个菜单界面,运行在240*128点阵的液晶上,有上、下、确定、返回4个按键。最初于2011年9月21日发表于阿莫电子论坛,后整理到自己的博客。
功能分析
1.菜单需要完成的功能是:
实时显示环境A中的若干传感器值(传感器数量大于液晶可以显示的最大值时,还需要翻页功能),实时显示环境B中的若干传感器值,时间显示和设置,系统信息和帮助信息。
2.菜单界面的布局
目前分为3种:
(1) 主菜单停留在屏幕上,下级菜单被选中后显示;
(2) 下级菜单显示时,上级菜单消失;
(3) 图形文字结合显示;
由于240128点阵的液晶,行最多显示15个1616汉字,列最多显示8个汉字,液晶可显示8行15列,采用布局(1):
3.从功能需求上看,这是一个简单的界面,配有简单的按键操作:
(1) “上”、“下”键在“环境A传感器值”,“环境B传感器值”,“时间设置”,“系统信息”4个菜单之间切换;
(2) “确定键”进入下级(子级)菜单,这里是显示信息,设置时间等;
(3) “返回键”返回上级(父级)菜单;
(4) “确定键”进入“时间设置”菜单后,“上下键”设置时间,“确定键”在年月日时分秒间切换,“返回键”完成时间设置;
基本功能完成后,进一步完善:
(5) 对于“系统信息”和“传感器值”这种类型的菜单,对应的子菜单只是显示某些信息,没有第二级需要继续选择的菜单项,故,当“当前菜单”为此类型时,不通过“确定键”直接显示相应的信息;
(6) 当“环境A”的传感器个数>8时,需要分2页显示,此时,按“确定键”翻页;
这份界面经历了3个版本,每次都有主要参考的代码资料。(说借鉴或着抄都可以,但也有我自己的构思,尤其是最后一个版本)
参考版本
第1个版本,参考的是《一个占用内存极少的菜单系统的实现》 http://goo.gl/hKBsK
作者公布了代码,但更令人钦佩的是很有耐心的写了份pdf教程,很有借鉴意义。但这个方案,菜单结构复杂增删不便,可维护性差,可移植性差。论坛上坛兄 a730598 《借阿莫的宝地开源个原创的LCD菜单内核》的帖子http://goo.gl/XhWao 分析的很细致。
第2个版本,同事给的,是一个多叉树的菜单设计。
关键在于菜单结构的设计,每个菜单都是一个节点,为了在父子兄菜单间切换,这个节点信息必须包含父、子、左兄弟、右兄弟节点链接,而且必须手动建立链接关系。但由于每个菜单的下级菜单个数不同,难以用统一的数据结构描述这种关系。
例如:a,b是兄弟菜单;a的左右兄弟都是b,b的左右兄弟都是a,但若a,b中间增加了c,变成了3兄弟a,c,b,上述关系必须更改:a的左兄弟是b,右兄弟是C,……,这些链接关系必须手动建立,一旦有增删,必须更新。
建立上述关系后,菜单间的切换很统一很方便:“确定键”当前菜单项->子菜单,若无子,则“当前菜单->功能函数”;“返回键”当前菜单->父菜单;“上”当前菜单->左兄弟;“下”当前菜单->右兄弟。但缺点也很明显。
第3个版本,参考 彭良清《基于节点编号的通用树状菜单设计方法与实现》http://goo.gl/hKBsK
这是我在下面会要重点说的,作者给出的方法是真好呀。我先copy下作者论文中的一些话:
界面设计的流程要把按键和菜单显示作为一个整体设计协同处理;
树形菜单以2层为限,选择级数超过3级,操作不便;
菜单布局,同一系统中应保持界面的一致性;菜单子项使用频率大,置前;
一个菜单(menu)是包含多个固定条目内容(菜单项menu item),并同时在屏幕上显示或消失的矩形窗口。
我的实现
菜单类型不同,对按键的响应不同。比如,某些菜单“确定键”进入子菜单,其它则执行某种功能(保存啦,退出啦,时间设置啦,输入参数啦,显示某些信息啦)。尤其是当按键少的时候,按键必须具有复用功能,问题是,怎么让程序知道此时此地“确定”是进入子菜单,它时它地“确定”是执行某个特殊的功能函数。(当按键多的时候,不通过菜单选择,通过按键,直接执行某种功能函数,这个本程序没有涉及到)
为了设计一个结构、流程清晰的界面,首先分析菜单的行为,对菜单进行分类:
1st. 选择型菜单:当然就是菜单间的切换了
2nd. 功能型菜单:
i. 功能1型,执行某个操作
ii. 功能2型,允许用户输入信息或设置(如时间设置)
iii. 功能3型,单向不受控的显示窗口\菜单(系统信息、帮助信息等)
然后建立菜单信息的结构:
typedef struct menu
{
uint8 ID; /*menu的唯一标识码*/
MenuInfo item[10]; /*结构体数组,本级menu包含的menuitem信息:显示位置,名称*/
uint8 items_num; /*本级menu的menuitem个数*/
/*和其它menu的关系,由程序完成,初始化NULL即可*/
struct menu *father, /*父辈menu*/
*son, /*长子menu*/
*obrother, /*兄menu elderbrother*/
*ybrother; /*弟menu youngerbrother*/
uint8 item_sn_cur; /*当前menuitem序号(serial number),0~(itemnum-1)*/
uint8 menu_type; /*menu类型*/
} Menu;
虽然这个二叉树的菜单设计,看上去和第2个版本多叉树类似,仍需要建立菜单节点之间的父子兄弟的关系。但这只是貌似相同,这2个有很大的区别。上一张图形象地显示了2者间的区别,更详细的见论文《基于节点编号的通用树状菜单设计方法与实现》中的图6,7。
多叉树菜单间的切换在第2个版本中有讲,它需要手动建立所有父子兄弟间的链接关系。
二叉树也需要建立这种关系,但这是通过一种ID编号规则(详见论文《基于节点》3(6))建立的,程序根据确立的ID编号规则初始化节点链接关系。显而易见的好处是,当我们需要增删节点或者改变节点位置时,只需要重新分配这个惟一的ID号即可。程序根据当前节点(菜单)的ID号,就可以寻找 到它的父子兄弟菜单,具体方法是,所有的菜单存在数组内的,通过for循环匹配ID,匹配即为当前菜单,当前菜单->父菜单,就找到了它的父菜单。
因此,本菜单的关键是,根据我们自己定义的ID编号规则,建立这样一个二叉树的菜单结构;然后,根据菜单类型,确立对按键响应的整个程序流程。
《基于节点》提出的程序流程是:
While (1) {
键值 = 获取按键值的函数( );
当前菜单 = 菜单选择函数(当前菜单,键值);
if (当前菜单 == 选择型菜单)
continue;
switch (当前菜单->ID) { /* 功能型菜单代码调用 */
case ID_1:
功能函数 ID_1();
break;
case ID_2:
……
}
}
菜单选择函数(当前菜单, 键值) {
switch (键值) {
case 上:当前菜单 = 当前菜单->兄; break;
case 下:当前菜单 = 当前菜单->弟; break;
case 确定:当前菜单 = 当前菜单->父; break;
case 返回:当前菜单 = 当前菜单->子; break;
}
return (当前菜单);
}
清晰,明了,易维护,易移植。
但是,我需要设置时间,我只有4个按键,因此,上下键必须复用,上述流程会有冲突。
我的改进是:
While (1) {
键值 = 获取按键值的函数( );
当前菜单 = 菜单界面执行函数(当前菜单,键值);
键值 = 空; /* 避免无键按下时,程序重复执行上次操作 */
}
菜单界面执行函数(当前菜单,键值) {
switch (菜单类型) {
case 选择型菜单:
当前菜单 = 菜单选择函数(当前菜单,键值);
break; /* 此函数和上一个流程的函数一样 */
case 功能1型(执行某个操作):
当前菜单 = 功能型菜单代码调用;
break;
case 功能2型(允许用户输入信息或设置(如时间设置)):
当前菜单 = 对应的功能函数;
break;
case 功能3型(单向不受控的显示窗口\菜单, 系统信息、帮助信息等):
当前菜单 = 对应的功能函数;
break;
}
return (当前菜单);
}
整个框架搭好之后,需要考虑细节,这些细节很容易在上述框架中实现:
- 屏幕刷新,有键按下时刷新界面,避免屏幕的闪烁;数据的更新通过不断写入的方式而非清屏刷新;
- 当前菜单的维护,分析所有可能改变当前菜单的函数,通过函数参数传递和返回值的方式,尽量避免使用全局变量;
- 通过ID编号规则初始化节点间的链接关系,必然有垃圾链接,比如,主菜单无父节点,最底层菜单无子节点,如何避免在这些地方跑飞;
- 对于多级菜单,父子菜单切换时高亮或反显的维护问题;
- 如何实现一.3.(5)(6)的扩展功能。
总结
上述提到的所有资料我放在了我的共享空间 http://goo.gl/4N1EQ
包括
- 主题为“多层菜单界面设计”的小论文和资料
- 我用到的液晶驱动芯片t6963C的论文和资料
- 这个液晶多层菜单界面的代码
这是对我着手写界面(或者说开始编程生涯)5个月的一点儿总结,当然也不是每天都在写,还有其它好多的工作呢。我还是有点进步哒。
不过硬伤是明显的,编程经验少、编写代码少、数据结构和算法知识缺陷、操作系统知识匮乏到简直不懂……
这个界面只能用在简单的系统上,对于复杂的系统则难以为继。
我真心希望有人能阅读我写的代码,即使风格很糟糕,然后说点什么,这对我很有帮助。
我的共享空间里可以下载到源码。
由于深刻和直接地体会到阅读没有注释的代码的痛苦和难耐,我在自己的代码里写了挺多注释,并且努力保持注释和代码的一致性;同样的原因,我也没有直接贴上代码了事,而是分析了我写代码的思路、程序的流程,留给自己,也留给需要的人;真心认为程序的流程远比代码本身更重要。
by weiyi.li http://li2.me SH FengXian 2011年9月21日2时0分