1引言
大多数嵌入式系统,仅提供几个按键和像素点较少的LCD,同时处理器运算能力有限(如8/16位单片机),不宜运行商用的GUI图形库(如uC/GUI、miniGUI、QT等),但仍然得为用户提供GUI功能。一个具有代表的硬件平台如下,提供6个输入按键:上移、下移、左移、右移、确定和取消;有一LCD,不限制物理尺寸与像素点数。如工业仪器需要设置参数一样,GUI需要支持用户输入,本文为满足这种需要设计图形控件。
2图形控件基类
面向对象是C++和JAVA之类的高级语言话题,在这里为什么需要用C语言来实现呢,原因在于嵌入式环境下往往不支持C++和JAVA语言,况且嵌入式工程师对C语言十分亲切,减少了学习成本。图形构件因为其关联性比较强(继承),往往将数据和操作组织在一起(封装),同时为高效实现经常将异构的控件统一操作(多态),基于面向对象来实现是顺其自然的。[1]
下面结合图形控件基类的设计探讨C语言实现面向对象的原理。
图1图形控件基类的设计
当我们用C语言将上述控件用结构体来组织时,实际上就完成了封装。这时,对一个对象的操作仅需要调用它的方法,如例1所示。
例1 设定义如下图形控件基类
typedef struct _graphic_ctrl {……} GRAPHIC_CTRL;
定义一个图形控件对象:GRAPHIC_CTRL stGCtrl;
可以操作这个对象的方法:gctrl_Create(&stGCtrl,NULL),这个方法根据对象的CtrlSort自动选择对应的派生类的创建函数。
如果另外需要定义一个类,如文本输入框,它除了继承基类外,还有自己的属性和方法,C语言的实现是将基类包含在子类的结构体中,例2说明了继承的实现。
例2文本输入类继承基类
typedef struct _text_edit
{
GRAPHIC_CTRL stGCtrl;
char *p_chBuf;
…… /* Other attributes of TextEditor */
} TEXT_EDIT;
定义一个文本输入框对象:TEXT_EDIT stTextEdit;它可以操作父类的方法,如gctrl_Create(&stTextEdit,NULL)。
一个按钮和一个文本输入框的回车响应动作是不同的,如何实现控件这种需求呢?改变它调用方法的实现细节,这就是多态的特性。
例3 重载RespBtn方法实现多态
void EnterBtn(void);
void EnterTextEdit(void);
GRAPHIC_CTRL stBtn;
TEXT_EDIT stTextEdit;
ConstructBtn(&stBtn, EnterBtn);
ConstructTextEdit(&stTextEdit, EnterTextEdit);
现在,终于可以展现一下面向对象带来的优势了。设系统响应用户的“回车”按键,软件可以不考虑控件对象的类别而统一调用RespBtn ()方法,这就实现了软件开发的精髓之一:高度抽象把复杂的事情简单化,例4描述了这种统一操作。
例4 统一响应控件的回车操作
gctrl_RespBtn((GRAPHIC_CTRL *)&stBtn);
gctrl_RespBtn((GRAPHIC_CTRL *)&stTextEdit);
有了上述C语言实现面向对象的基础后,我们再来考虑设计图形控件就变得容易多了。
3按钮
如右图4个按钮:
按钮是简单的控件,它直接继承GRAPHIC_CTRL父类就能实现。当执行Create()方法时,先将字符串打印在区域中间,后画出一个矩形;Destroy()方法是空操作,不需要实现;执行Active()方法时,将字符串和矩形区域反白显示;Inactive()方法同Create()方法一样,打印字符串和绘制矩形;RespBtn()方法中,它响应“确定”按键的具体动作,针对上/下/左/右方向按键它向调用者(如窗口)返回切换控件的消息;扩展的Enter()方法实现不同按键的“确定”动作响应,从而实现多态技术。
图2按键类的设计
4下拉菜单
Create()方法创建的下拉选择框如右图,
除绘制矩形框和框内字符串外,还需要打印框外的文本(定义为STATIC_TEXT),因而在类的属性中需要添加STATIC_TEXT的指针。Active()方法表现的效果是,和按钮激活的动作相似;Inactive()方法与Create()方法相同,Refresh()方法与Create()方法也相同。
在响应按键消息时就有区别了。如果“确定”键已经被按下,那么控件将呈现图3左边的选择项,这时需要计算是否有空间向下绘制选择项(可能会超过LCD的下端),如果没有空间将向上绘制选择项。“上/下按键”可以操作选择项,图3右显示了“下按键”操作的结果,因此也需要一个记录上下键位置的数据。再次按下“确定”键完成选择操作,如,而“取消”键随时可以退出操作过程。
图3下拉选择项
如果“确定”键没有被按下,“上/下/左/右按键”的响应简单为向调用者(如窗口)返回切换控件的消息。
很明显,响应按键操作需要一个状态机,因此类的属性中将添加BtnStat。
控件的Destroy()方法就变得繁忙了,它有三件事要干:首先向控件返回被选择项的索引,如果是“确定”键将返回有效下标,如果是“取消”键将返回无效下标;其次清除选择项绘制的整个区域;最后得把被选择项区域破坏的其他控件恢复。
恢复被破坏的控件将是一个值得商榷的话题,理想的方法是先将被破坏的区域保存起来再予以恢复,但这种方法需要操作像素VRAM区,我们将采用这种方法。它的硬件实现原理是LCD的控件器一般都支持READ_LCD_DATA()操作,当指定特定区域的位置和大小后,就可以读取该区域的像素数据并予以保存;软件上需要实现类似“压栈/弹栈”的操作,栈的深度依赖于需要保存VRAM的递归次数。有了软硬件的支持后,先把即将被破坏的区域予以压栈保存,当需要恢复该区域时,调用弹栈函数恢复该区域。
总结下拉选择框的类定义如图4所示:
图4下拉选择框类的设计
5文本输入框
右图显示了一个文本输入框的实例:,
除去矩形框前后的静态文本显示外,它和按钮的显示是很相似的。
文本输入框需要一缓冲区来存储用户的输入,此外响应“确定”按键时调用软键盘,当软键盘退出时拷贝输入的字符串,它还需要限定输入字符串的个数,检查输入值的有效性,当输入非法时需要提示正确的数值范围。它的类定义如图5所示。
图5文本输入框类的定义
6 软键盘
图6显示了一个软键盘的实例,它分为四部分:标题栏、输入栏、操作集和选项集。标题栏显示输入对象的名称;输入栏显示当前已经输入的文本集,光标可以在输入字符的任意位置;操作集从左到右是:光标左移、光标右移、退格、下一页和回车,光标左移和光标右移控制光标在输入栏中进行移动,退格可以删除光标前的字符,下一页就是显示下一页选项集(如果有分页),回车退出软键盘并返回所输入的字符串,选项集是用户能进行选择操作的字符集。
图6软键盘实例
当软键盘Create()时,它从调用者的对象中获取标题栏字符串,再根据选项集的数目计算页数与行数;Destroy()方法与下拉选择框相同,除清除自己外还需要通知刷新窗口内的所有控件。RespBtn()方法依赖不同的按键操作:上/下按键修改行索引,左/右按键修改列索引,当索引被修改时,先注销前一个选项(表现为失焦),后激活当前选项(表现为获焦)。当前激活项是选项集时,“确定”按键将该选项的字符添加到输入栏中;当前激活项是操作集时,“确定”按键执行对应的操作。“取消”按键可以随时退出软键盘。
软键盘类的定义如图7所示:
图7软键盘类的定义
7 图形控件的使用
上述设计了4种常用的图形控件,现在讲解调用它们的一般方法。
首先需要为该图形对象申明并开辟内存,如:
static BTN s_stBC91MExit =
{
.stGCtrl =
{
.chID = BC91M_EXIT,
.chItemNum = 1,
.chSelIx = 0,
.nAddrX = CHAR_LEN * 33,
.nAddrY = BTN_CTRL_Y,
.nSizeW = BTN_CTRL_W,
.nSizeH = CHAR_WIDE,
.pp_chBtnStr = &a_strBC91MBtnStr[3],
},
};
其次需要调用方法初始化该图形对象,如下所示:
gctrl_ConsBtn(&s_stBC91MExit, BC91MBtnEnter);
创建窗口时也会调用控件的创建函数:
gctrl_Create(&s_stBC91MExit, NULL);
当用户按键选中该按钮时,gctrl_Active(&s_stBC91MExit, NULL)会被调用,以便于激活该对象;同样,gctrl_Inactive(&s_stBC91MExit, NULL)用于注销该对象;如果本按钮是激活对象,那么任何用户按键消息将会传递给它,即调用方法gctrl_RespBtn(&s_stBC91MExit,
&uRtn);最后当窗口退出时会调用按钮的销毁方法,gctrl_Destroy(&s_stBC91MExit, NULL)。
7 结束语
本文设计的轻量级嵌入式图形控件已经在某工业控制产品中稳定使用多年,该产品选用TRULY公司320x240像素的LCD。采用面向对象的设计,使软件系统容易开发;简单化的设计使系统异常稳定;另外占用资源很少,这是商业GUI无法比拟的。需要了解LCD硬件连接与驱动以及窗口系统的实现,可以参见姊妹篇论文《一种轻量级嵌入式GUI设计》。
参 考 文 献
[1] Steve McConnell. Code Complete. Second Edition. 金戈等译。电子工业出版社,2006.3.
附注:需要源代码的朋友请邮件联系本人,Email: [email protected]
作者简介:
蒋俊,男,硕士研究生,现任长沙市锐米通信科技有限公司CEO。
从事通信研究与嵌入式开发10年,主攻微功率无线网络。
精通LoRa无线扩频通信,无线星型/树型/MESH网络设计;
通晓Contiki,Linux,uC/OS-II,OSAL等操作系统;
熟悉ARM,DSP,STM8,PIC,PC104等处理器;
擅长AD,RF等集成IC开发。
Web: www.rimelink.com
EMail: [email protected]
QQ群:35212129