sochen1987 发表于 2012-12-7 10:11:21

站在巨人的肩膀—GUI构架(可便捷添加和删除菜单)

本帖最后由 sochen1987 于 2012-12-7 13:42 编辑

/**
******************************************************************************
*GUI构架浅谈
*本文档描述了个人关于GUI的一些心得
*移植性及便捷任意的菜单添加和删除是设计考虑的首要因数
*同时考虑占用尽可能少的RAM和ROM
*sochen QQ148265029
*设计思路参考了《傻孩子》《通用菜单构架》等网络经典GUI构架
******************************************************************************
*/
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
GUI特点
->事件触发模式,GUI任何动作均由外部事件触发
*例如刷新,可由外部的时钟通知一次刷新事件
*例如翻页,可由按键或者通讯命令通知一次翻页事件
*等等......你能想象到都可用事件触发模型替代

->复杂的显示类型,通常包含以下几种
*输出界面,如-关于我们-
*输入界面,如-时钟设置- (**:**)

->每一PAGE菜单都有以下属性
*兄弟菜单(平级)
*父菜单
*子菜单

->基于以上特点,个人在研发过程中遇到十分棘手的问题有以下几点
*GUI与输入设备很难做到统一
*复杂的显示类型及树形的结构很难做到便捷的任意菜单添加和删除
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
如何解决GUI和输入设备的统一性问题
->所有的输入设备可以统一看成虚拟设备,在此结论下可得以下几点
*任何触发GUI动作的实体设备,如按键,通讯,采样值更新,屏幕刷新均得到各自的事件映射
*需要一个事件发生器,由实体设备使用
*需要一个事件解析器,由GUI实现MYSELF逻辑
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
如何实现GUI便捷的任意菜单添加和删除
->我理解的菜单最小单元PAGE,原因有以下几点:
*PAGE单元下能有较完整的对象统一,PAGE再往下细分则很难统一(这个是资源和实现方式的矛盾)

->菜单的树形结构
*链表操作,一个节点变更则引起网络一簇的变更,不利于任意菜单添加和删除
(链表操作对于编程者的C语言能力也是一种考验)
*上下级之间出入口唯一

->如何用数组代替菜单的树形结构
*离散思维启发:一根根枝干即可凑成一颗完整的树

->下文的数据结构基于以上两点考虑设计
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
数据结构

/* 一页菜单对象结构 */
struct page_object         
{
    u8 item_num;                         /* 当前层下菜单项数 */
    void (*exe)();                         /* 执行函数 */
    void (*event)();                      /* 事件处理函数 */
    struct page_object *enter;   /* 进入下一层地址 */
    struct page_object*back;      /* 返回上一层地址 */
};
/* GUI对象结构 */
struct gui_object{
    struct page_object *pre_page;      /* 当前菜单指针 */
    u8 pre_item;                              /* 当前项数 */
    u8 flash;                                    /* 刷新标志 标志具体的刷新事件 */         
    u8 event;                                     /* 事件 */
};
/* 菜单事件 可根据自己的需求添加 */
enum GUI_EVENT{
    IDEL = 0,
    FLASH,
    ENTER,
    BACK,
    UP,
    DOWN,
    MENU,
};
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
实体
->gui_structure.c
*每根枝干的实体,拼凑成一个完整的树
*编程者在此文件实现每根枝干实体的框架(数据结构,菜单网络等)
*示例为以下结构
/*
   L1         L2             L3
->fun1                                       P1
   fun2 -> fun2_1 -> fun2_1_1       P2
               fun2_2   fun2_1_2       P3
                              fun2_1_3       P4
*/
const struct page_object L1_buf = {
    /* item_numexe       event         enter         back       */
    {         2,      fun1,    fun1Event,   L1_buf,      L1_buf},
    {         2,      fun2,    fun2Event,   L2_buf,   &(L1_buf)},
};
const struct page_object L2_buf = {
    /* item_numexe            event               enter             back       */
    {       2,      fun2_1,    fun2_1Event,&(L3_buf),   &(L1_buf)},
    {       2,      fun2_2,    fun2_2Event,&(L1_buf),   &(L1_buf)},
};
const struct page_object L3_buf = {
    /* item_num   exe             event                   enter         back*/
    {         3,      fun2_1_1,   fun2_1_1Event,    &(L1_buf),    L2_buf},
    {         3,      fun2_1_2,   fun2_1_2Event,    &(L1_buf),    L2_buf},
    {         3,      fun2_1_3,   fun2_1_3Event,    &(L1_buf),    L2_buf},
};
->同级菜单的切换可维护item_num实现
->上下级切换可维护*enter和*back实现
->优于《傻孩子》构架:const型数据结构减少了RAM的要求,一般单片机ROM>RAM
->优于《通用菜单构架》:便捷的任意菜单添加和删除,《通用菜单构架》改动一处则所有的表需重新制作
->需要明确的一点:任何菜单构架下,不同的菜单页面及菜单维护均需编程者实现,本文更侧重于怎样把GUI模块化,让结构更清晰,易于添删

->gui_event.c
*提供的操作可根据需求自己添加 亦可根据自己的需求自行设计
*消费事件                                 宏GUI_COST_EVENT将事件清除          
*确认事件发生并返回事件类型    宏GUI_EVENT_OCCUR         
*邮递事件                                 void Gui_Post(enum GUI_EVENT event)   
*++

->gui.c
*所有GUI实体可在该文件下实现(包括具体的fun,event)
*每page均有exe 与event函数入口,函数实体与gui_structure.c内一一对应
*需要include的组件驱动driver,显示功能模块module等可根据自己的需求设计
*以下函数实现了GUI的运行
/*
void Gui_Run(void)
{
    if (GUI_EVENT_OCCUR){
      (*(*(gui.pre_menu)).event)();
      GUI_COST_EVENT;
      (*(*(gui.pre_menu)).exe)();
    }
}
*/
->有事件发生,则会有相应的EXE得到执行
->event屏蔽了不同输入设备引起的代码一致性
->所有的细节实现文档没有体现
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
不足
->多事件并发,需要重新设计GUI事件部分代码
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
完结
->不给具体的代码和工程,不知道大家理解文档的意思没有,希望能给用到的朋友一些启发
->个人认为编程者根据别人好的思想,自己实现代码更有助于理解和进步
->有更好的建议希望留言,技术的进步源于交流

ksniper 发表于 2012-12-7 10:18:42

GUI和菜单 做起来很多门道

Gorgon_Meducer 发表于 2012-12-7 10:21:57

哈哈,欢迎连载。

sanliuyaoling 发表于 2012-12-7 14:22:49

支持楼主!!!!!!!!!!!!!!!!!!

bbsview 发表于 2012-12-7 15:16:54

来张效果图看看先,呵呵

Excellence 发表于 2012-12-7 15:27:32

把函数指针用好就可以做出GUI。
方法很多种。

Gorgon_Meducer 发表于 2012-12-7 16:21:27

本帖最后由 Gorgon_Meducer 于 2012-12-7 16:23 编辑

从专用系统来说,这是一个很不错的结构,很赞~
不过我想提供另外一个思路给你:
请思考一下,菜单的本质是什么?菜单只是一个逻辑网络,每一个网络的节点就是一个菜单项(menu_item_t),
若干菜单项的集合就是一个菜单(menu_t)。这是菜单最基本的结构了。那么菜单与显示逻辑有关系么?有什么关系呢?
其实菜单与显示没有半毛钱关系。如果把菜单的实现逻辑看成一个模块,则这个模块与外界有两类接口,一类,接受
外界的“逻辑输入”,很高兴得看到你用枚举类型定义了逻辑的输入,我想你已经体会到逻辑输入与具体输入设备
之间的关系;另外一类接口就是将自己的一部分菜单逻辑暴露出去,比如——当前菜单以及菜单的所有菜单项,这些
暴露出去的菜单逻辑由外部的GUI部分读取,并根据具体的应用需求显示出来。
显然,这个模块是个黑盒子,一方面外部通过输入接口将获取的逻辑输入送到模块中来驱动菜单状态迁移;另一方面
外部可以通过一个专门的接口读取到显示菜单所需的最小信息:当前菜单,及其所有的菜单项,当前用户光标的位置。

根据上面的分析,显然一个菜单模块中不应该包含和显示逻辑直接相关的内容,一个菜单项menu_item_t只应该包含
三类信息:
1、内容(比如文字、帮助信息、其它任何附加的resource指针)
2、处理必要逻辑所需的函数指针,这里的函数指针最好推荐用“类模板”的形式来构建——也就是由用户来定义,
   菜单逻辑自己是不会调用这些函数指针的。
3、维护菜单逻辑结构所需链式结构。

经过上面的分析,实际上我已经给你指出了一个全新的思路——一个抽象的更好,封装的更完整的菜单逻辑。

sochen1987 发表于 2012-12-7 16:41:19

Gorgon_Meducer 发表于 2012-12-7 16:21 static/image/common/back.gif
从专用系统来说,这是一个很不错的结构,很赞~
不过我想提供另外一个思路给你:
请思考一下,菜单的本质是 ...

对的.如果把它看作一个模块,数字式的模块只能有这样的结构:输入和输出及自身逻辑维护.由于模块化势必带来内存和储存器的多余消耗以抵消特异的情况,所以这是一个矛盾。如果在资源几乎可以忽略的情况下我会选择模块化为主,但在资源有限的情况下,我会选择部分的模块化和部分的效率考虑(特异情况).谢谢提供新的思路.

Gorgon_Meducer 发表于 2012-12-7 16:52:03

本帖最后由 Gorgon_Meducer 于 2012-12-7 16:55 编辑

sochen1987 发表于 2012-12-7 16:41 static/image/common/back.gif
对的.如果把它看作一个模块,数字式的模块只能有这样的结构:输入和输出及自身逻辑维护.由于模块化势必带 ...

认为模块化会消耗额外资源,这是不一定的。关键看你怎么模块化。实际上模块化只是一种思维模式,并不一定会导致
额外的资源消耗——希望你能打破定式思维。我建议的这种新封装模式不会比你现在的封装方式消耗更多的资源,实际
是只要你把GUI的操作代码从菜单模块中挪出去就足够了……真的只是一种思维模式的差异。

sochen1987 发表于 2012-12-7 17:08:52

Gorgon_Meducer 发表于 2012-12-7 16:52 static/image/common/back.gif
认为模块化会消耗额外资源,这是不一定的。关键看你怎么模块化。实际上模块化只是一种思维模式,并不一定 ...

恩,赞同你的意见,思维的差异。我说的模块化资源差异可能不单单指GUI吧,也是我平常工作遇到的一些困惑的地方。

mcu_lover 发表于 2012-12-7 17:09:12

这么好的文章,没人讨论,是在可惜。建议置酷。
对于输入事件来说,抽象成事件方式是比较可行的,这一点在很多嵌入式GUI
里面都得以体现。
楼主位文档提及的方式不错,可以说结合了目前网上公开的几种方式的优点。
但是,仍然需要用户手工填写大量内容。尤其是界面绘制与数据处理最终还是会糅合到一起。
不方便复用。基本还是属于比较落后的方式。

其实,有些概念先要弄清楚。你是否需要一个用户界面,通俗讲,在你的项目里设计的
这个产品到底为啥配备一个液晶显示器。到底有多少参数,多少信息需要提供给用户。
那些参数(运行参数)是可以修改的,那些参数(状态参数)是可以查看的。规划好这些之后
再来着手用户界面的设计。

归根到底,屏幕上面显示的内容大抵如下,无论再复杂,嵌套再深,最终只会归于以下三种状态:
1. 信息显示。读取某个变量值,显示给用户,如表征系统运行状态的变量,如通信参数等等。
   抑或干脆纯粹显示一些文本信息,如本机软体版本,公司信息等等。
2. 值修改。这一点应该是大家处理比较多的情况。用户需要修改一些变量值,如通信参数,
   系统各种运行变量等等,具体哪些值修改取决于各位设计的系统。
3. 执行一个任务/触发一个时间。其实这里与值修改在某些场合有一点点重合。如修改一个BOOL
   型值时,可能它的值ON时候启动了电机,OFF时候关闭了电机。而大部分情况时候是调用了
   系统预先编写的一个功能函数,或者触发一个事件给系统。

清楚了上面概念后,再来看看有关显示的:
显示类型方面其实归根到底也没有几项
1-> 数值/文本 显示
    数值显示分十进制,十六进制,定点小数等等。这里面格式根据自己需要制定
2-> 数值/文本/密码 输入
    这里的输入项即各种参数的输入,二进制量处理,如ON/OFF, SET/RESET等等。
    在嵌入式设备上比较好的方式是采用弹出子窗口方式处理输入。即不需要复杂
    的窗口管理机制,又使得输入界面对用户而言非常直观。
    当然,实际情况下大家用的方式都是直接闪烁一个光标,标识当前项处于修改
    状态。相比子窗口输入机制,这种方式其实是很笨拙的。
3-> 一些绘制性内容,如画几条线,填充几个块块,以使得界面看起来丰满,不至于
    单调。其实这里的内容与屏幕组织方式有关。个人建议是GUI组织方式以屏幕为单位。

一般说来动态添加或者删除显示项目,在90%以上的系统里是不需要的,至少目前我接触到的很少
需要这种特性。如果非常实现,也有各种折衷的办法,简单点,为每个项目增加一个visible属性
即可,这种情况下动态添加/删除其实是伪装的,即显示与否,采用这种情况,在绘制项目时候
需要考量项目显示位置,否则在各显示项目之间可能会出现空白。第二种折衷办法是采用配置文件
方式。这种方式可以实现显示与具体内容分离。实现起来可能麻烦一些。如果这些都还不行,那就
只好把RAM不当数,用链表去实现吧。

所以,最好的组织方式不是以菜单方式,而是以界面/屏幕方式进行组织。尤其对于嵌入式系统而言。
如果有人曾经做过裸机界面开发(论坛有朋友很早前已经发过裸机屏幕编写方式)一定会对此深有感触。
在某个屏幕下接收输入,刷新界面来与用户交互。这里最好的办法就是状态机去处理,这种裸机屏幕
编写方式就是一个状态机嵌套一个状态机,状态机的返回值是可以通信的....傻孩子应该对此深有感触。
好了,不讲了,听楼下开讲吧。

Gorgon_Meducer 发表于 2012-12-7 17:13:07

sochen1987 发表于 2012-12-7 17:08 static/image/common/back.gif
恩,赞同你的意见,思维的差异。我说的模块化资源差异可能不单单指GUI吧,也是我平常工作遇到的一些困惑 ...

一样的,关键是思想,以及自己对语言工具的理解程度。不单单指GUI。

sochen1987 发表于 2012-12-7 17:13:36

mcu_lover 发表于 2012-12-7 17:09 static/image/common/back.gif
这么好的文章,没人讨论,是在可惜。建议置酷。
对于输入事件来说,抽象成事件方式是比较可行的,这一点在 ...

恩,不同的角度看问题会发现新的视野.

Gorgon_Meducer 发表于 2012-12-7 17:17:32

mcu_lover 发表于 2012-12-7 17:09 static/image/common/back.gif
这么好的文章,没人讨论,是在可惜。建议置酷。
对于输入事件来说,抽象成事件方式是比较可行的,这一点在 ...

我最近研究的方向是“去接口化”,也就是实现“应用代码只开发一次,在不修改上层
逻辑的情况下通过 去接口化 的方式来提供更高级别的优化”。至于GUI……大家讨论吧,
需要我的时候,点我名就好。

fy024 发表于 2012-12-7 17:22:35

我写过一个类似的,但没有考虑到事件处理

mcu_lover 发表于 2012-12-7 17:24:16

Gorgon_Meducer 发表于 2012-12-7 17:17 static/image/common/back.gif
我最近研究的方向是“去接口化”,也就是实现“应用代码只开发一次,在不修改上层
逻辑的情况下通过 去接 ...

嘿嘿,我最近研究方向是“组态化”,最小限度减少人工编码,依托PC工具快速可视化开发,当然应用相关的代码无论那种方式都需要用户去编写的。
很少有产品交互只是几行菜单就OK了的。当然那些采用字符液晶,或者点阵液晶分辨率在128*32以下的设备除外。

Gorgon_Meducer 发表于 2012-12-7 17:26:31

mcu_lover 发表于 2012-12-7 17:24 static/image/common/back.gif
嘿嘿,我最近研究方向是“组态化”,最小限度减少人工编码,依托PC工具快速可视化开发,当然应用相关的代 ...

组态化我也很感兴趣,不过我把这个方向推广的更开,应该不叫组态了,而是软构建开发。
基本上准备模仿EDA工具的思路来做,现在基本理论框架已经有了,就是想等时间来试试看,
估计等写完计划C的那本书,我后面的专注点就会移动到这个软构件系统上去的。

mcu_lover 发表于 2012-12-7 17:30:13

Gorgon_Meducer 发表于 2012-12-7 17:26 static/image/common/back.gif
组态化我也很感兴趣,不过我把这个方向推广的更开,应该不叫组态了,而是软构建开发。
基本上准备模仿EDA ...

期待。你现在构想的这个系统会不会涉及到脚本语言呢?貌似如果不用脚本语言,到时候有些东西做起来不是很灵活的。

Gorgon_Meducer 发表于 2012-12-7 18:12:54

本帖最后由 Gorgon_Meducer 于 2012-12-7 18:18 编辑

mcu_lover 发表于 2012-12-7 17:30 static/image/common/back.gif
期待。你现在构想的这个系统会不会涉及到脚本语言呢?貌似如果不用脚本语言,到时候有些东西做起来不是很 ...

脚本语言已经选好了,基本上就是XAML了,它的Dependency Property很好用的。思路是用XAML描述构件,在局部允许加载不同的插件来处理不同用途的代码生成,
或者叫做构件管理——比如状态机插件可以让你直接用状态机写代码;比如通讯插件可以让你图形化的设计通讯协议(数据帧,解析状态机,消息地图管理);比如
基于专门芯片的插件可以让你快速的生成特定外设的硬件驱动代码——基本上就是搭建好公共框架,以后以维护插件的形式来提供丰富的扩展。

hamipeter 发表于 2012-12-7 19:18:32

不错的想法,拜读了

mcu_lover 发表于 2012-12-7 19:18:37

Gorgon_Meducer 发表于 2012-12-7 18:12 static/image/common/back.gif
脚本语言已经选好了,基本上就是XAML了,它的Dependency Property很好用的。思路是用XAML描述构件,在局 ...

听起来好诱人,非常期待。
有空了我也去了解下XAML。我有一个构想,就是有一种语言专门用来描述GUI,然后这种语言可以被编译之后,生成的代码是目标平台的C代码。

LSZD 发表于 2012-12-7 19:37:23

拜读!!!!!!!!!!!!!!!

LSZD 发表于 2012-12-7 19:37:42

拜读!!!!{:smile:}

oldmen 发表于 2012-12-7 19:40:06

好文章,标记。不知道为什么这个帖子不能收藏

cyr_hongfeng 发表于 2012-12-7 22:39:31

傻孩子的思想已经完整体现在他那个菜单程序里,就是菜单处理和显示处理分开,你可以简单理解为菜单处理一个函数显示处理一个函数{:lol:}
我看了楼主位的文章,什么事件处理,屏蔽处理,没必要,简单用一个switch搞定了,
struct page_object         
{
    u8 item_num;                         /* 当前层下菜单项数 */
    void (*exe)();                         /* 执行函数 */
    char even_style
    struct page_object *enter;   /* 进入下一层地址 */
    struct page_object*back;      /* 返回上一层地址 */
};
.......
{         2,      fun1,    even_style_1,   L1_buf,      L1_buf},
{         2,      fun2,    even_style_1,   L2_buf,   &(L1_buf)},
{       2,      fun2_1,   even_style_3,&(L3_buf),   &(L1_buf)},
{       2,      fun2_2,    even_style_4,&(L1_buf),   &(L1_buf)},

mcu_lover 发表于 2012-12-7 23:32:43

cyr_hongfeng 发表于 2012-12-7 22:39
傻孩子的思想已经完整体现在他那个菜单程序里,就是菜单处理和显示处理分开,你可以简单理解为菜单处理一个 ...

你研究的层次不够。

Gorgon_Meducer 发表于 2012-12-8 00:07:11

cyr_hongfeng 发表于 2012-12-7 22:39 static/image/common/back.gif
傻孩子的思想已经完整体现在他那个菜单程序里,就是菜单处理和显示处理分开,你可以简单理解为菜单处理一个 ...

当时写的时候还没有诸位想的这么透彻,如果说当时的代码已经分开了,
应该是巧合。毕竟当时对模块封装没有现在的理解程度。认真来说,
当时的代码是没有很好封装的,只不过的确是考虑过show menu函数
根据具体显示模式不同而可以使用不同的实现模式。我这么说的一个
证据就是我的菜单逻辑实现部分居然调用了sub这个函数指针,严格来说
当用户触发enter逻辑的时候,菜单模块应该触发一个事件并传递当前
菜单项给外部,由模块外部逻辑来决定如何处理菜单项的内容。
不过你能注意到这个巧合说明你应该是比我当时理解的透彻的多^_^
谢谢分享

Gorgon_Meducer 发表于 2012-12-8 00:09:38

mcu_lover 发表于 2012-12-7 19:18 static/image/common/back.gif
听起来好诱人,非常期待。
有空了我也去了解下XAML。我有一个构想,就是有一种语言专门用来描述GUI,然后 ...

貌似要万丈高楼平地起啊。

zzjjhh250 发表于 2012-12-8 09:20:36

围观学习。我们电源工作者要学习

cumt_123456 发表于 2012-12-24 08:30:04

{:lol:}谢谢,学习

bbssilverkey 发表于 2012-12-25 15:13:08

上个图啦,虽然好东西

ljt80158015 发表于 2012-12-25 15:21:55

事件 是不是 搞的有点复杂了?

3DA502 发表于 2012-12-27 01:21:34

现在刚到把状态和 事件 区分开的水平

看到到处乱指的指针就不好理清头绪

//测试ID

Feco 发表于 2012-12-27 08:10:06

学习了!

liumaojun_cn 发表于 2012-12-27 08:54:52

没时间看,用的时候再看。

limxuzheng 发表于 2013-3-26 11:38:58

的确是高手呢

milo112 发表于 2013-4-2 14:08:28

学习了。。

Hz01800475 发表于 2013-4-7 09:50:08

Gorgon_Meducer 发表于 2012-12-7 18:12 static/image/common/back.gif
脚本语言已经选好了,基本上就是XAML了,它的Dependency Property很好用的。思路是用XAML描述构件,在局 ...

神器啊,梦想之。

dsjsjf 发表于 2013-9-5 17:34:59

mark               

heiselpy 发表于 2013-9-5 21:00:13

能不能给个完整的代码,看的好费劲。。。{:mad:}

dongfo 发表于 2013-10-26 16:49:49

mark 很好的文章

lovely-teddy 发表于 2013-10-27 08:11:37

楼主的思路很好,可否给个简单的例子或者可以编译的框架

enovo2468 发表于 2013-11-9 14:55:12

学习一下{:smile:}

zt_ustb 发表于 2013-12-14 13:37:46

思想倒是明白了,但是还有很多东西要做。

Feco 发表于 2014-1-7 21:49:41

学习了,记号~~

fshunj 发表于 2014-1-7 23:58:55

记号mark之

犯戒和尚 发表于 2014-1-16 10:22:51

学习学习,可惜没图!

chaquetn 发表于 2014-2-25 19:12:49

拜读!!!!!!!!!!!!!!!
膜拜

swp 发表于 2014-2-28 10:00:37

我在想如果拥有了visualstate里面提到的第三方gui开发工具altia faceplate 之后,gui,菜单一类的开发、测试应该更加高效。
页: [1]
查看完整版本: 站在巨人的肩膀—GUI构架(可便捷添加和删除菜单)