2dmin 发表于 2012-12-19 23:01:07

基于二叉树的多层的液晶菜单界面设计 && 资料

工作需要写一个菜单界面运行在240*128点阵的液晶上,有上、下、确定、返回4个按键。
一、功能分析
      1. 菜单需要完成的功能是:
      实时显示环境A中的若干传感器值(传感器数量大于液晶可以显示的最大值时,还需要翻页功能),实时显示环境B中的若干传感器值,时间显示和设置,系统信息和帮助信息。
      2. 菜单界面的布局
      目前分为3种:
                (1) 主菜单停留在屏幕上,下级菜单被选中后显示;
                (2) 下级菜单显示时,上级菜单消失;
                (3) 图形文字结合显示;
                由于240*128点阵的液晶,行最多显示15个16*16汉字,列最多显示8个汉字,液晶可显示8行15列,采用布局(1),
                如图示:
液晶界面基本布局图 (原文件名:液晶界面基本布局图.png)
      3. 从功能需求上看,这是一个简单的界面,配有简单的按键操作:
                (1) “上”、“下”键在“环境A传感器值”,“环境B传感器值”,“时间设置”,“系统信息”4个菜单之间切换;
                (2) “确定键”进入下级(子级)菜单,这里是显示信息,设置时间等;
                (3) “返回键”返回上级(父级)菜单;
                (4) “确定键”进入“时间设置”菜单后,“上下键”设置时间,“确定键”在年月日时分秒间切换,“返回键”完成时间设置;
      基本功能完成后,进一步完善:
                (5) 对于“系统信息”和“传感器值”这种类型的菜单,对应的子菜单只是显示某些信息,没有第二级需要继续选择的菜单项,故,当“当前菜单”为此类型时,不通过“确定键”直接显示相应的信息;
                (6) 当“环境A”的传感器个数>8时,需要分2页显示,此时,按“确定键”翻页;

这份界面经历了3个版本,每次都有主要参考的代码资料。(说借鉴或着抄都可以,但也有我自己的构思,尤其是最后一个版本)

2dmin 发表于 2012-12-19 23:01:47

二、参考版本
第1个版本,参考的是《一个占用内存极少的菜单系统的实现》 http://goo.gl/hKBsK
作者公布了代码,但更令人钦佩的是很有耐心的写了份pdf教程,很有借鉴意义。但这个方案,菜单结构复杂增删不便,可维护性差,可移植性差。论坛上坛兄a730598 《借阿莫的宝地开源个原创的LCD菜单内核》的帖子http://goo.gl/XhWao 分析的很细致。

第2个版本,同事给的,是一个多叉树的菜单设计。
关键在于菜单结构的设计,每个菜单都是一个节点,为了在父子兄菜单间切换,这个节点信息必须包含父、子、左兄弟、右兄弟节点链接,而且必须手动建立链接关系。但由于每个菜单的下级菜单个数不同,难以用统一的数据结构描述这种关系。
如图:
多叉树菜单、二叉树菜单 (原文件名:多叉树菜单、二叉树菜单.jpg)
例如: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),并同时在屏幕上显示或消失的矩形窗口。

2dmin 发表于 2012-12-19 23:02:02

三、我的总结
菜单类型不同,对按键的响应不同。比如,某些菜单“确定键”进入子菜单,其它则执行某种功能(保存啦,退出啦,时间设置啦,输入参数啦,显示某些信息啦)。尤其是当按键少的时候,按键必须具有复用功能,问题是,怎么让程序知道此时此地“确定”是进入子菜单,它时它地“确定”是执行某个特殊的功能函数。(当按键多的时候,不通过菜单选择,通过按键,直接执行某种功能函数,这个本程序没有涉及到)

为了设计一个结构、流程清晰的界面,首先分析菜单的行为,对菜单进行分类:
                1st. 选择型菜单:当然就是菜单间的切换了
                2nd. 功能型菜单:
                        i. 功能1型,执行某个操作
                        ii. 功能2型,允许用户输入信息或设置(如时间设置)
                        iii. 功能3型,单向不受控的显示窗口\\菜单(系统信息、帮助信息等)
然后建立菜单信息的结构:
       typedef struct menu
      {
                uint8 ID;                  /*menu的唯一标识码*/
                MenuInfo item;   /*结构体数组,本级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:
                     ……
                  }
                }
               
                菜单选择函数(当前菜单, 键值)
                {
                  swithc (键值)
                  {
                  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 (当前菜单);
                }      
               
整个框架搭好之后,需要考虑细节,这些细节很容易在上述框架中实现:
      1. 屏幕刷新,有键按下时刷新界面,避免屏幕的闪烁;数据的更新通过不断写入的方式而非清屏刷新;
      2. 当前菜单的维护,分析所有可能改变当前菜单的函数,通过函数参数传递和返回值的方式,尽量避免使用全局变量;
      3. 通过ID编号规则初始化节点间的链接关系,必然有垃圾链接,比如,主菜单无父节点,最底层菜单无子节点,如何避免在这些地方跑飞;
      4. 对于多级菜单,父子菜单切换时高亮或反显的维护问题;
      5. 如何实现一.3.(5)(6)的扩展功能。

2dmin 发表于 2012-12-19 23:02:23

四、结束啦
上述提到的所有资料我放在了我的共享空间http://goo.gl/4N1EQ
包括 1. 主题为“多层菜单界面设计”的小论文和资料
       2.我用到的液晶驱动芯片t6963C的论文和资料
       3. 这个液晶多层菜单界面的代码
这是对我着手写界面(或者说开始编程生涯)5个月的一点儿总结,当然也不是每天都在写,还有其它好多的工作呢。我还是有点进步哒。
不过硬伤是明显的,编程经验少、编写代码少、数据结构和算法知识缺陷、操作系统知识匮乏到简直不懂……
这个界面只能用在简单的系统上,对于复杂的系统则难以为继。
我真心希望有人能阅读我写的代码,即使风格很糟糕,然后说点什么,这对我很有帮助。
我的共享空间里可以下载到源码。
由于深刻和直接地体会到阅读没有注释的代码的痛苦和难耐,我在自己的代码里写了挺多注释,并且努力保持注释和代码的一致性;同样的原因,我也没有直接贴上代码了事,而是分析了我写代码的思路、程序的流程,留给自己,也留给需要的人;真心认为程序的流程远比代码本身更重要。

Leekp 2011年9月21日2时0分

wllis 发表于 2012-12-25 23:45:21

顶一个

longxuekai 发表于 2014-3-9 09:14:04

这也属于ARM

wangdanq 发表于 2023-10-10 09:48:34

谢谢楼主分享
页: [1]
查看完整版本: 基于二叉树的多层的液晶菜单界面设计 && 资料集锦