教程不断更新中:http://www.armbbs.cn/forum.php?mod=viewthread&tid=98429
第48章 emWin6.x对话框基础知识
上个章节为大家普及了控件的基础知识,本章节再普及对话框的基础知识,有了这两部分的基础知识就方便我们后面讲解各个控件类型了。使用了对话框后,一方面可以方便我们使用GUIBuilder小工具创建界面,因为GUIBuilder仅支持以对话框为模板的界面创建,另一方面可以方便我们对控件进行管理。
48.1 初学者重要提示
48.2 什么是对话框
48.3 对话框的基本原理
48.4 创建对话框
48.5 对话框API函数使用注意事项(重要)
48.6 总结
48.1 初学者重要提示
1、 对话框比较重要,项目中用到的地方很多,初学者务必要掌握,对于基础概念可以先有一个感性认识,随着后面学习再逐渐加强认识,另外,重点学习48.5小节。
2、 对话框相关的API函数只有四个,下图是中文版手册里面API函数位置:
下图是英文版手册里面API函数的位置:
48.2 什么是对话框
简单的说,emWin的对话框是以窗口Window或者FrameWin为主体,这两个必须创建一个,这就是一个最简单的对话框。如果还要添加其他控件,直接通过对话框的资源列表就可以很方便的添加各种控件,这样就组成了一个稍复杂的对话框。对于初学者来说,理解了这点已经够用了。
48.3 对话框的基本原理
48.3.1 阻塞式和非阻塞式对话框
对话框可以分为阻塞式和非阻塞式。
阻塞式对话框会阻塞执行的线程(任务)。默认情况下,对话框有输入焦点,用户必须先关闭它,线程才能继续执行。因此,如果对话框为阻塞式,则表示只有在对话框关闭后,所使用的函数GUI_ExecDialogBox()或GUI_ExecCreatedDialog()才会返回值。另外特别注意,阻塞式对话框不会禁用所显示的其他对话框,换句话说,阻塞式对话框并非模态对话框,设置模态需要专门调用函数WM_MakeModal进行设置。
非阻塞式对话框则不会阻塞调用的线程,创建对话框后,函数会立即返回值。
最后,还有一点需要注意的是切勿从回调函数中调用阻塞式函数。否则,可能会导致应用程序出问题。
48.3.2 输入焦点
窗口管理器能记住用户使用触摸屏、鼠标、键盘或用其他方式最终所选择的窗口,对话框或者控件。有一点要特别的注意,只有聚焦的窗口,对话框或者控件才可以接收键盘的输入消息。如果要将对话框内的输入焦点移至下一个焦点控件,可以使用GUI_KEY_TAB按键消息,如果要向后移动,则可以使用GUI_KEY_BACKTAB按键消息。
输入焦点是实现外置键盘或者类似键盘的输入设备操作窗口或者控件的关键,后面第62章专门为大家讲解这个问题。
48.3.3 对话框回调函数
对话框就是一个窗口,它接收消息的方式与系统中其他所有窗口一样。大多数消息由对话框的回调程序自动处理,而其他消息则传递到建立对话框时所指定的回调程序,官方手册将这个回调程序称之为Dialog procedure,其实就是回调函数,与我们前面讲解窗口的回调函数一样。
48.3.4 对话框消息
相比前面章节中讲解窗口的回调消息,消息类型WM_INIT_DIALOG是对话框专有的。对话框回调函数通常使用该消息来初始化控件,也可以在这个消息里面创建子窗口,控件或者子对话框。 另外emWin官方手册中将消息WM_NOTIFY_PARENT也说成是对话框的附加消息,这个有些牵强了,因为这个消息不是对话框专有的,而是所有的窗口通用的消息类型。
48.4 创建对话框
创建对话框需要两个基本要素:资源列表和对话框回调函数,资源列表用来定义所要创建的控件,回调函数用来处理各种消息类型。一旦具备这两个要素,则只需调用函数GUI_CreateDialogBox(非阻塞是对话框)或GUI_ExecDialogBox(阻塞式对话框)就能创建对话框。
这里只是给大家讲解一下各个部分的原理,具体的创建方法可以看前面GUIBuilder和AppWizard的使用方法,这两个小软件都是以对话框为模板创建控件的,后面章节讲解各个控件时还会继续为大家加强对话框方面的认识。
48.4.1 资源列表
对话框可以基于阻塞(使用GUI_ExecDialogBox())或非阻塞(使用GUI_CreateDialogBox())方式创建。首先,必须定义一个资源类别,以指定在对话框中要创建的控件,下面是一个创建好的资源列表实例:
static const GUI_WIDGET_CREATE_INFO _aDialogCreate[] = { { FRAMEWIN_CreateIndirect, "Dialog", 0, 10, 10, 180, 230, FRAMEWIN_CF_MOVEABLE, 0 }, { BUTTON_CreateIndirect, "OK", GUI_ID_OK, 100, 5, 60, 20 }, { BUTTON_CreateIndirect, "Cancel", GUI_ID_CANCEL, 100, 30, 60, 20 }, { TEXT_CreateIndirect, "LText", 0, 10, 55, 48, 15, TEXT_CF_LEFT }, { TEXT_CreateIndirect, "RText", 0, 10, 80, 48, 15, TEXT_CF_RIGHT }, { EDIT_CreateIndirect, NULL, GUI_ID_EDIT0, 60, 55, 100, 15, 0, 50 }, { EDIT_CreateIndirect, NULL, GUI_ID_EDIT1, 60, 80, 100, 15, 0, 50 }, { TEXT_CreateIndirect, "Hex", 0, 10, 100, 48, 15, TEXT_CF_RIGHT }, { EDIT_CreateIndirect, NULL, GUI_ID_EDIT2, 60, 100, 100, 15, 0, 6 }, { TEXT_CreateIndirect, "Bin", 0, 10, 120, 48, 15, TEXT_CF_RIGHT }, { EDIT_CreateIndirect, NULL, GUI_ID_EDIT3, 60, 120, 100, 15 }, { LISTBOX_CreateIndirect, NULL, GUI_ID_LISTBOX0,10, 10, 48, 40 }, { CHECKBOX_CreateIndirect, NULL, GUI_ID_CHECK0, 10, 140, 0, 0 }, { CHECKBOX_CreateIndirect, NULL, GUI_ID_CHECK1, 30, 140, 0, 0 }, { SLIDER_CreateIndirect, NULL, GUI_ID_SLIDER0, 60, 140, 100, 20 }, { SLIDER_CreateIndirect, NULL, GUI_ID_SLIDER1, 10, 170, 150, 30 } };
对话框中要创建的任何控件都必须使用<WIDGET>_CreateIndirect()函数来间接创建,这个在前面第47章47.7.1小节进行了专门讲解,这里不再赘述。
48.4.2 对话框回调函数
上述48.4.1小节的资源列表再配合下面的回调函数就可以创建对话框了:
/********************************************************************* * * 对话框回调函数 */ static void _cbCallback(WM_MESSAGE * pMsg) { switch (pMsg->MsgId) { default: WM_DefaultProc(pMsg); } }
对话框的创建函数采用阻塞式API函数GUI_ExecDialogBox:
GUI_ExecDialogBox(_aDialogCreate, GUI_COUNTOF(_aDialogCreate),&_cbCallback, 0, 0, 0);
所生成的对话框显示效果如下(实际外观则取决于用户配置和系统默认设置):
创建对话框后,所有资源列表中的控件都将可见。但他们仅仅只是显示出来,没有任何实际功能,因为回调函数里面什么都没有做,只有默认的系统处理函数WM_DefaultProc。
48.4.3 配置初对话框回调消息WM_INIT_DIALOG
下面将上述48.4.2小节回调函数中加入对话框初始化消息WM_INIT_DIALOG,代码如下:
/********************************************************************* * * 对话框回调函数 */ static void _cbCallback(WM_MESSAGE * pMsg) { int NCode, Id; WM_HWIN hEdit0, hEdit1, hEdit2, hEdit3, hListBox; WM_HWIN hWin = pMsg->hWin; switch (pMsg->MsgId) { case WM_INIT_DIALOG: /* Get window handles for all widgets */ hEdit0 = WM_GetDialogItem(hWin, GUI_ID_EDIT0); hEdit1 = WM_GetDialogItem(hWin, GUI_ID_EDIT1); hEdit2 = WM_GetDialogItem(hWin, GUI_ID_EDIT2); hEdit3 = WM_GetDialogItem(hWin, GUI_ID_EDIT3); hListBox = WM_GetDialogItem(hWin, GUI_ID_LISTBOX0); /* Initialize all widgets */ EDIT_SetText(hEdit0, "EDIT widget 0"); EDIT_SetText(hEdit1, "EDIT widget 1"); EDIT_SetTextAlign(hEdit1, GUI_TA_LEFT); EDIT_SetHexMode(hEdit2, 0x1234, 0, 0xffff); EDIT_SetBinMode(hEdit3, 0x1234, 0, 0xffff); LISTBOX_SetText(hListBox, _apListBox); WM_DisableWindow (WM_GetDialogItem(hWin, GUI_ID_CHECK1)); CHECKBOX_Check( WM_GetDialogItem(hWin, GUI_ID_CHECK0)); CHECKBOX_Check( WM_GetDialogItem(hWin, GUI_ID_CHECK1)); SLIDER_SetWidth( WM_GetDialogItem(hWin, GUI_ID_SLIDER0), 5); SLIDER_SetValue( WM_GetDialogItem(hWin, GUI_ID_SLIDER1), 50); break; default: WM_DefaultProc(pMsg); ) } }
加入了对话框初始化消息后,所有控件都包含了它们各自的初始值,显示效果如下:
48.4.4 定义对话框行为
对话框上的控件有了各自的初始化数值后,就可以为各个控件添加具体的功能了,也就是在回调函数里面添加相应控件的回调消息,代码如下所示,这里有个感性认识即可:
/********************************************************************* * * 对话框回调函数 */ static void _cbCallback(WM_MESSAGE * pMsg) { int NCode, Id; WM_HWIN hEdit0, hEdit1, hEdit2, hEdit3, hListBox; WM_HWIN hWin = pMsg->hWin; switch (pMsg->MsgId) { case WM_INIT_DIALOG: /* Get window handles for all widgets */ hEdit0 = WM_GetDialogItem(hWin, GUI_ID_EDIT0); hEdit1 = WM_GetDialogItem(hWin, GUI_ID_EDIT1); hEdit2 = WM_GetDialogItem(hWin, GUI_ID_EDIT2); hEdit3 = WM_GetDialogItem(hWin, GUI_ID_EDIT3); hListBox = WM_GetDialogItem(hWin, GUI_ID_LISTBOX0); /* Initialize all widgets */ EDIT_SetText(hEdit0, "EDIT widget 0"); EDIT_SetText(hEdit1, "EDIT widget 1"); EDIT_SetTextAlign(hEdit1, GUI_TA_LEFT); EDIT_SetHexMode(hEdit2, 0x1234, 0, 0xffff); EDIT_SetBinMode(hEdit3, 0x1234, 0, 0xffff); LISTBOX_SetText(hListBox, _apListBox); WM_DisableWindow (WM_GetDialogItem(hWin, GUI_ID_CHECK1)); CHECKBOX_Check( WM_GetDialogItem(hWin, GUI_ID_CHECK0)); CHECKBOX_Check( WM_GetDialogItem(hWin, GUI_ID_CHECK1)); SLIDER_SetWidth( WM_GetDialogItem(hWin, GUI_ID_SLIDER0), 5); SLIDER_SetValue( WM_GetDialogItem(hWin, GUI_ID_SLIDER1), 50); break; case WM_KEY: switch (((WM_KEY_INFO*)(pMsg->Data.p))->Key) { case GUI_ID_ESCAPE: GUI_EndDialog(hWin, 1); break; case GUI_ID_ENTER: GUI_EndDialog(hWin, 0); break; } break; case WM_NOTIFY_PARENT: Id = WM_GetId(pMsg->hWinSrc); /* Id of widget */ NCode = pMsg->Data.v; /* Notification code */ switch (NCode) { /* React only if released */ case WM_NOTIFICATION_RELEASED: if (Id == GUI_ID_OK) { /* OK Button */ GUI_EndDialog(hWin, 0); } if (Id == GUI_ID_CANCEL) { /* Cancel Button */ GUI_EndDialog(hWin, 1); } break; /* Selection changed */ case WM_NOTIFICATION_SEL_CHANGED: FRAMEWIN_SetText(hWin, "Dialog - sel changed"); break; default: FRAMEWIN_SetText(hWin, "Dialog - notification received"); } break; default: WM_DefaultProc(pMsg); } }
48.5 对话框API函数使用注意事项(重要)
对话框主要有以下四个API:
从应用的角度来看,这几个函数都比较容易,看emWin官方手册对这些函数的说明并结合我们本章节的讲解基本就会使用了,我们这里对这几个函数使用注意事项简要说明下,因为初学者经常在这几个点犯错误。
- WM_HWIN GUI_CreateDialogBox(const GUI_WIDGET_CREATE_INFO * paWidget,
int NumWidgets, WM_CALLBACK * cb,
WM_HWIN hParent, int x0,
int y0);
- 这个函数是非阻塞的,创建完毕后就立即返回了,但此时对话框还不能立即显示出来,需要用户调用了函数WM_Exec,GUI_Exec或者GUI_Delay才可以显示出来。
- 对话框返回的句柄就是对话框资源列表里面第一个控件的句柄,如果是框架窗口FrameWin,那么返回的句柄就是框架窗口的句柄,如果是窗口Window,那么返回的就是窗口的句柄,而且对话框资源列表的第一个控件必须是框架窗口或者窗口,其它的控件都不可以是第一个,这点要特别注意。
- 此函数的回调函数参数cb是对话框客户区窗口的回调函数,这个该怎么理解呢,如果对话框的主体是框架窗口FrameWin,那么对话框客户区窗口就是框架窗口的客户区窗口,框架窗口是由主窗口和客户区窗口(主窗口的子窗口)组成的。如果对话框的主体是窗口Window,那么对话框客户区窗口就是此窗口。
- 鉴于上面的第2条和第3条,如果要给对话框的回调函数发消息,框架窗口为主体的对话框一定要使用函数WM_GetClientWindow获取客户区句柄,然后以此句柄为参数操作相关的API函数,切不可以直接使用对话框返回的句柄。窗口为主体的对话框可以直接使用对话框返回的句柄,前面章节讲解自定义消息发送函数WM_SendMessageNoPara还专门强调了这个问题。
- int GUI_ExecCreatedDialog(WM_HWIN hDialog);
调用函数GUI_CreateDialogBox创建了对话框后,可以调用这个函数执行对话框,不过是阻塞方式,必须关闭了对话框后此函数才可以返回。
- int GUI_ExecDialogBox(const GUI_WIDGET_CREATE_INFO * paWidget,
int NumWidgets,
WM_CALLBACK * cb,
WM_HWIN hParent,
int x0,
int y0);
此函数等效于执行完毕函数GUI_CreateDialogBox后再调用函数GUI_ExecCreatedDialog,也是阻塞式的,关闭了对话框后,此函数才可以返回。
- void GUI_EndDialog(WM_HWIN hDialog, int r);
此函数用于关闭对话框,其中第二个参数r是用于阻塞式对话框创建和执行函数GUI_ExecDialogBox,第二个参数设置的数值就是函数GUI_ExecDialogBox的返回值。比如参数r设置为数值1,那么对话框关闭后,函数GUI_ExecDialogBox返回值就是1。
48.6 总结
有了本期和前面的教程介绍后,后面具体的讲解每个控件时就会方便很多。