作者:UCGUI
邮箱:UCGUI@163.com
主页:http://www.UCGUI.com
版本:v1.0.0.0
时间:2006/12/01<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
问题:
case WM_NOTIFY_PARENT:
Id = WM_GetId(pMsg->hWinSrc); // Id of widget
NCode = pMsg->Data.v; // Notification code
switch (NCode) {
case WM_NOTIFICATION_RELEASED: // React only if released
if (Id == GUI_ID_OK) { // OK Button
GUI_MessageBox("Message", "caption", GUI_MESSAGEBOX_CF_MOVEABLE);
}
if (Id == GUI_ID_LISTBOX0 ){
pObj=LISTBOX_H2P(hListBox);
LISTBOX_GetItemText(hListBox,1,ListItemName,20);
GUI_MessageBox("the sel item is",ListItemName,GUI_MESSAGEBOX_CF_MOVEABLE);
// MESSAGEBOX_Create(ListItemName, "the sel item is", GUI_MESSAGEBOX_CF_MOVEABLE); //改成此句正确..
}
在如上代码当中, 在点击ListBox时,执行GUI_MessageBox()会导致弹出N多对话框,以及"GUITASK.c: GUI_Lock() _EntranceCnt overflow "的错误提示. 但是点击按钮时, 执行GUI_MessageBox()没有问题, 这是什么原因??
论题分析:
深入UCGUI的消息处理机制当中,详细介绍一下为什么在UCGUI390版本中,仅有Button控件的点击支持弹出对话框,其它的控件均会导致出错呢?
UCGUI324版本当中, 我们详细的剖析了为什么在点击Button消息中弹出对话框会导致出错, 详细的分析可见"UCGUI窗体管理及消息处理机制分析.pdf"(可到www.ucgui.com/bbs当中下载).
[原创]有关UCGUI的几篇技术文章下载 [ 2 3 4]
http://www.ucgui.com/bbs/dispbbs.asp?boardID=1&ID=131&page=1
这里不再重复的讲先前讲过的东西了, 下面我们将大略介绍如下几点:
一. UCGUI中的基本设计策略----微型.
与其它一些同样是嵌入式的GUI图形系统相比, UCGUI很明显的一大区别是:微型; 这微型的特征主要体现在两点:节省空间, 包括代码空间与程序运行占用空间.
-----代码空间占用少, 是指其对于同样的功能, 其实现简单,结构简单,设计简单,复杂的功能放弃;
-----程序运行占用空间少, 是指支持GUI运行时, 所消耗的变量空间小, 这对于本来RAM空间就紧张的小型嵌入式应用, 是非常的重要的. 程序运行消息消耗的空间小, 主要有两个方面:
[1]. 消息处理不用队列, 而是采取两个变量, 一个变量记载当前消息,一个变量记载先前消息,当前消息从外部输入驱动中获取更新, 处理消息时与先前消息进行比较, 处理完消息再将当前消息更新到先前消息变量中.这样处理起来,比起采用消息队列, 就省了不知道多少空间了,因为在启用MOSUE类设备时,移动消息是非常多的.不采用消息队列, 也导致了UGGUI中的所有的消息处理都是同步的, 但这对于UCGUI来说也不是什么严重的问题, 并不是必须的功能; 没有消息队列, 则每个消息的处理不能占用大量的时间, 否则严重影响消息处理的灵敏度,造成消息的丢失.但是这些问题都是可以接受的, 因来带来的空间效率带有帮助了.
[2]. 单一运行绪:UCGUI运行的时候, 仅有一条执行路径, 也就是单一运行绪.这与MINIGUI多线程版是有很大的差别的,熟悉MINIGUI的朋友都知道MINIGUI分了三个版本, 多进程版/多线程版/单一版.多线程版分如下线程:
-----用户程序是一个线程.
-----桌面程序是一个线程.
-----事集收集是一个线程.
-----时间Timer是一个线程.
-----创建窗口也可以单独启用一个线程.
分了这些程, 当然有些好处, 如计时器/消息接收不至被消息处理阻塞; 但是UCGUI中没有这些复杂的机制, 从而少了很多的线程同步以及与其它系统的相关性(便于移植).而且在资源占用了又少了一层, 节省了线程资源.
[3]. 剪切处理, 窗口系统的剪切处理有两个要素, 一是时间效率;二是空间效率. UCGUI中采取的是空间策略, 每进行一次图形绘制,都必须处理剪切; 在时间效率优先的系统当中, 会计算过后就保存每个窗口的剪切矩形, 如果是在这个窗口上绘图, 就无须每一次绘图操作都进行剪切处理, 只须要判断是否处于剪切矩形之内即可, 当然这个判断也是很讲究策略的, 因为剪切矩形是一个系列,如果是绘制一个矩形, 这个矩形被分布在几个剪切矩形当中, 这个时候也能采取两种办法:一是逐点判断之后画点;二是再进行一次剪切计算,剪切出可以绘制的矩形.这两个方法其实也体现了空间与时间的问题, 不过第二种方法在时间上很优, 在空间上也仅是占用一时, 所以是比较优的. 但是如果要计算过每个窗体的剪切矩形后, 要保存起来, 供窗体上再次作画时用, 这个空间占用则是比较大的.
二. 为什么UCGUI的消息处理机制会导致多对话框的问题.
同上面关于UCGUI中的消息机制的描述, 具体来看一看为什么会导致多话框支持上的问题.
[1]. UCGUI中创建对话框后, 会形成一个独立的消息LOOP, 独自的进行消息处理. 我们已经说过, UCGUI中的所有消息处理都是同步的,而且是单一执行绪的. 所以这样一来, 明显就带来的一个问题, 试想如下一个点击消息中弹出窗体的流程:
----1. 获取外部驱动的消息, 更新了当前消息变量, 设当前消息为CurrentMsg, 先前旧消息为PrevMsg, 比较新旧消息,如单击发生的坐标及按下与否的状态.
----2. 如果是外部驱动产生的新消息(如单击消息), 处理单击消息, .
----3. 进入单击消息处理, 发送单击消息给对应的当前窗体, 根据当前的点击坐标取得单击发生位置的窗体句柄.
----4. 对应的消息窗体处理消息, 弹出对话框.
----5. 更新消息, CurrentMsg="PrevMsg", 再转入第1步, 检测消息.
这是一个整体的消息处理完成的过程, 这个过程是一直LOOP, 反复检测的. 想想如果是新弹出一个对话框, 又进入了新的消息LOOP, 那么可以肯定的是, 在新的消息LOOP处理当中, 由于上一次消息LOOP的第5步[更新消息]没有完成, 则新消息LOOP中当中就会反复处理这个没有更新的消息了. 然且这个过程是一个嵌套的过程, 一层的消息LOOP引发下一层的消息LOOP, 但都无法完成在短时间内最后一步的消息更新.
这里我说短时间内, 是指的新的消息还没有到来的时候, 因为新的消息一到来, 就终止了不断处理旧消息弹出对话框的过程.
[2]. UCGUI中详细的消息处理流程:
在最新版的UCGUI390版, 消息的处理主要分成四个部分.
----模态窗口消息分发, 检测当前窗体是否与模态窗体有关, 即是否是模态窗体本身或者其子孙窗体, 用WM__IsInModalArea()检测;这一步控制了消息的分布, 实现模态窗体的功能就是这一句代码了.
----弹起与按下状态变化的消息分发(WM_PID_STATE_CHANGED消息), 这个消息的分布在按下MOUSE或者弹起MOUSE的时刻发生.
----只要是按下状态, 以及按下与弹起状态变化的消息分发(WM_TOUCH), 这个消息在MOUSE按下或者弹起, 以及一直处于按下时发送, 比如你按下一个BUTTON, 一直不放, 当移动到另一个BUTTON时, 会看到焦点发生了切换到了新的BUTTON上, 就是这个消息导致的.另外这个消息会在消息发生的新旧窗体交界时, 会最后向原来的旧窗体发最后一个WM_TOUCH消息(状态为弹起), 发送这个消息为了让原来的旧窗体改变自己的状态(如取消息焦点框等), 正如上面你按住MOUSE滑动新的BUTTON, 则原来的BUTTON的焦点状态取消, 不再高亮显示, 就是这个最后的消息发送导致的.
----移动的消息分发(WM_MOUSEOVER), 这个消息是在打开了GUI_SUPPORT_MOUSE宏配置的时候才分发, 这个消息是很大量的, 只要MOUSE移动都会产生.
三. UCGUI324与UCGUI390版本在处理多对话框上的几点不同之处.
新旧版本主要有如下几点不一样:
[1]. 增中了模态窗体的支持, 这个在第二点中已经描述过了.
[2]. 增加了WM_MOUSEOVER消息.
[3]. 增加了WM_PID_STATE_CHANGED消息.
但是如果仅仅这个地方一点小变化, 也不足以让UCGUI390支持在按钮点击消息中弹出对话框, 还有如下几个变化:
[1]. 创建对话框的变化.
具体的问题, 请用户自己查看代码.
-----UCGUI324版中, 在创建对话框时, 用一个全局的回调函数_cbDialog(), 作为每个对话框的回调函数, 用一个全局变量_cb来保存用户对话框自己指定的回调函数. 然后在_cbDialog()函数中再调用用户对话框自己的回调函数(存在_cb变量中). 这显然是有问题的, 如果创建了新的对话框, 则这些全局变量就被新的赋值取代了, 就调用不到用户原来对话框指定的回调函数了.
-----UCGUI390版中, 就不再用什么全局变量了, 直接用用户指定的回调函数, 就不存在创建新的对话框后, 用户原来对话框指定的回调函数无法调用到的问题了.
[2]. BUTTON按钮的点击消息处理的变化.
-----主要的差别在于, 新版当中加上了Button是否BUTTON_STATE_PRESSED状态的判断, 即在处理按下BUTTON弹起后, 要向父窗体发送出WM_NOTIFICATION_RELEASED消息前, 调用WIDGET_AndState(hObj, BUTTON_STATE_PRESSED);来取消Button的按下状态, 就是这一点, 就使得即使有未更新的消息再次处理, 也不会导致再次给父窗体发送Button弹起的消息, 从而避免了反复陷入弹出对话框, 消息处理中消息一直无法更新的问题.
[3].除了Button控件之外, 其它控件在单击消息中弹出对话框, 会出问题.
-----比较上面Button的处理, 问题就相当容易理解了, 因为其它控件没有关于BUTTON_STATE_PRESSED这种类似状态的判断, 所以导致了未更新的消息反复的处理, 在短时间内弹出一堆的对话框出来, 并且由于反复的消息LOOP嵌套, 导致加锁的没有解锁, 从而报出"GUITASK.c: GUI_Lock() _EntranceCnt overflow "的错误提示.
四. 如何改进UCGUI390, 以使其支持在任意控件的点击消息当中弹出对话框.
[1]. 弹出对话框时, 如果没有什么特别的问题, 可以使用模态对话框, UCGUI390中支持模态对话框, 由于是模态对话框, 那么先前未更新的旧窗体的消息, 就无法再进行处理了, 所以不会陷入反复的弹出对话框中去.
创建模态对话框:
{
WM_HWIN hWinDlg = MESSAGEBOX_Create(" You Win!!! ", "Win", GUI_MESSAGEBOX_CF_MOVEABLE);
WM_MakeModal(hWinDlg);
GUI_ExecCreatedDialog(hWinDlg);
}
[2]. 如果是不采用弹出模态对话框, 我想另外的办法就是, 改进每个控件, 对于MOUSE弹起消息转发父窗体时, 加上类似BUTTON的控制, 加上一个状态的识别, 就可以了, 但这要改动几乎每个控件的_OnTouch()处理.
[3]. 前面说过, 正是因为弹出的对话框进入了新的消息LOOP, 导致原来的对话框的消息LOOP无法再执行下去, 无法更新旧消息变量. 那么我们就可以采取不让新建的对话框开启新的消息LOOP, 这很容易, 打开对话框时如下:
MESSAGEBOX_Create(ListItemName, "the sel item is", GUI_MESSAGEBOX_CF_MOVEABLE);
这里仅创建对话框, 而不执行 GUI_ExecCreatedDialog(hWin), 就不会开启新的消息LOOP; 那消息LOOP放到哪里去呢? 直接放到MainTask()当中去:
while (1) {GUI_Exec();}
文章评论(0条评论)
登录后参与讨论