最近在用ucGUI写一个终端的界面,碰到多个对话框之间相互调用,会弹出提示"Max. message nesting exceeded, Message skipped."的错误。分析原因后,发现是由于在对话框的回调函数中调用GUI_ExecDialogBox()这个函数创建对话框的缘故,因为GUI_ExecDialogBox()函数是创建对话框并且马上执行对话框的回调函数,一直到该对话框的动作处理完成之后才返回,所以在对话框的回调函数中调用这个函数就相当调用了一个同步函数。如果在两个对话框的回调函数中利用这个函数相互调创建对方的话,就会导致无限的同步函数嵌套调用,就会进入一个死循环,回调函数就永远无法返回。但是在做界面开发的时候,很容易碰到对话框之间相互创建调用的问题。要解决这个问题,最初我想了一个比较笨的方法,利用一个while死循环来执行所有的对话框创建,对话框之间的切换,利用标志位来设置。大致的流程如下:
_cbDialog1(WM_MESSAGE *pMsg) {
switch (pMsg->MsgId)
{
......
case WM_KEY:
{
int Key = ((WM_KEY_INFO*)(pMsg->Data.p))->Key;
switch(Key){
case WM_ENTER:
GUI_EndDialog();
isDlg2 = 1;break;
}
}
break;
......
}
}
_cbDialog2(WM_MESSAGE *pMsg)
{
switch (pMsg->MsgId)
{
......
case WM_KEY:
{
int Key = ((WM_KEY_INFO*)(pMsg->Data.p))->Key;
switch(Key)
{
case WM_ENTER:
GUI_EndDialog();
isDlg1 = 1;
break;
}
}
break;
......
}
}
int main()
{
GUI_Init();
while
{
if(isDlg1)
{
GUI_ExecDialogBox();
isDlg1 = 0;
}
if(isDlg2)
{
GUI_ExecDialogBox();
isDlg2 = 0;
}
}
}
后来看了ucGUI4.04的文档,发现对话框API的参考是这样描述的:
GUI_CreateDialogBox(); 创建一个非阻塞对话框
GUI_ExecDialogBox(); 创建一个阻塞对话框
既然GUI_CreateDialogBox()是用来创建非阻塞对话框的,那么就可以直接在对话框的回调函数用直接调用GUI_CreateDialogBox()来创建想要的对话框,可是我直接用GUI_CreateDialogBox()函数在对话框的回到函数中创建另外一个对话框,并没有达到预期的效果,仔细一想,发现GUI_CreateDialogBox()这个函数只是创建一个对话框的对象,返回对话框的句柄,那么肯定还需要一个使这个对话框的显示驱动。为什么GUI_ExecDialogBox()能够创建之后就显示?对比这2个函数的源代码,发现驱动对话框显示的地方是下面这一段代码:
while (!DialogStatus.Done) {
if (!GUI_Exec()) {
GUI_X_WAIT_EVENT();
}
}
用来显示对话框的函数是GUI_Exec(),参看ucGUI4.04手册关于GUI_Exec()函数的描述是
Executes callback functions (typically redrawing of windows)
执行回调函数(一般用来重画窗口)
这么一来,如果要显示函数GUI_CreateDialogBox()创建的对话框,就必须调用GUI_Exec()这个函数,而且是要在一个while循环中调用。如果还是在回调函数中先调用GUI_CreateDialogBox(),然后利用上面这段while循环来执行对话框的显示,就跟直接调用GUI_ExecDialogBox()函数一样,没什么区别。那么要实现多个非模态对话框的创建和显示,应该怎么操作?方法如下:
_cbDialog1(WM_MESSAGE *pMsg)
{
switch (pMsg->MsgId)
{
......
case WM_KEY:
{
int Key = ((WM_KEY_INFO*)(pMsg->Data.p))->Key;
switch(Key)
{
case WM_ENTER:
GUI_EndDialog();
hDlg2 =GUI_CreateDialogBox(dialog2);break;
}
}
break;
......
}
}
_cbDialog2(WM_MESSAGE *pMsg)
{
switch (pMsg->MsgId)
{
......
case WM_KEY:
{
int Key = ((WM_KEY_INFO*)(pMsg->Data.p))->Key;
switch(Key)
{
case WM_ENTER:
GUI_EndDialog();
hDlg1 =GUI_CreateDialogBox(dialog1);
break;
}
}
break;
......
}
}
int MainTask()
{
GUI_CreateDialogBox(dialog1);
while(1)
{
GUI_Exec();
}
}
肯定有人会问为什么GUI_CreateDialogBox()仅仅创建了个对话框,对话框的句柄也没有传给GUI_Exec()这个函数,GUI_Exec()怎么会知道要重画那个窗口,执行那个窗口的回调函数。
开始我也想不通,后来跟踪GUI_Exec()的代码,发现它调用了_DrawNext()这个函数,
个人推断此函数会更新画所有以创建过的窗口,没有仔细分析,想了解跟深入的可以自己阅读源代码。
因此,可以创建无数个非模态对话框,然后用这个while(1){GUI_Exec();}来显示这些创建的对话框。
这么一来,对话框之间的切换就不会有嵌套调用的问题了,因为GUI_CreateDialogBox()创建完成之后会马上返回,
对话框回调函数的执行由GUI_Exec()来进行。对于使用ucGUI还需要注意的一点就是消息的发送,
ucGUI中所有窗口之间消息的发送都要经过WM_SendMessage(),而这个函数发送的消息全部都是同步消息,
在ucGUI中没有MFC中的PostMessage()消息函数。GUI_EndDialog()函数在对对话框进行关闭的时候,
也是通过对对话框发送同步消息来实现关闭的。
本人使用的ucGUI版本是网上流传的3.90版和3.98版,2个版本都验证过,以上方法可行。
以上这些内容是对近一个月来使用ucGUI碰到问题的分析和总结,由于刚接触ucGUI没多久,
难免有错误,错误之处,还望高手指点迷津,欢迎拍砖。