接上节, 对话框(一)
对话框和普通窗体最大的区别在于它使用了资源来描述其界面特征。这里的资源编译后被存放在了exe或者dll的“某个位置”,类似的资源还有许多,比如:Icon, Menu, Bitmap, Accelerator, String等。这些资源都有自己的标识,对话框资源的标识为RT_DIALOG。通过下面的方法,可以在编译后的模块中获得它,注意最后需要释放该资源:
LPCDLGTEMPLATE lpDialogTemplate = NULL;
HGLOBAL hDialogTemplate = NULL;
HINSTANCE hInst = AfxFindResourceHandle(lpszTemplateName, RT_DIALOG);
HRSRC hResource = ::FindResource(hInst, lpszTemplateName, RT_DIALOG);
hDialogTemplate = LoadResource(hInst, hResource);
if (hDialogTemplate != NULL)
lpDialogTemplate = (LPCDLGTEMPLATE)LockResource(hDialogTemplate);
ASSERT(lpDialogTemplate != NULL);
//actual do something with the use of the resource handle…
// free resource
UnlockResource(hDialogTemplate);
FreeResource(hDialogTemplate);
围绕上述基本步骤,CDialog类中有许多创建对话框的方法,最终是调用了CreateDlgIndirect API方法。值得注意的是,该方法将不会产生WM_CREATE消息。对话框创建时的消息序列可以参看《窗口消息》一文。
对对话框而言,通常最先接触的消息就是WM_INITDIALOG。顾名思义,响应它进行初始化,此时,所有控件都已经创建出来。对该消息的默认响应由CDialog类的OnInitDialog虚方法完成。该方法在AfxDlgProc中被调用。AfxDlgProc做为参数,在CreateDlgIndirect API中传入,用来接收发送到对话框的消息。AfxDlgProc方法中,只处理了WM_INITDIALOG消息。所有消息都会经过窗口的WndProc,WM_INITDIALOG也不例外。CDialog中使用HandleInitDialog方法响应该消息,并在其中调用CWnd的Default方法,实际上就是调用DefWindowProc来处理,而该方法调用了CallWindowProc API(该方法用来处理Subclass 窗口过程未处理的消息,即:使用subclass前的窗口函数来处理,MFC中被称为Super Wnd Proc, 在CWnd中使用m_pfnSuper成员表示),这个super WndProc即为AfxDlgProc。
在《窗口消息》的“MFC消息分发”一节中,讲到消息是如何从消息泵中出来,并在MFC的窗体对象间传递的。其中,AfxCallWndProc是主要的方法之一,也就是在该方法中,消息的目标窗口对象的WndProc方法被调用。同时,我们也注意到,在该方法之前和之后,分别有一个针对WM_INITDIALOG的处理,即:_AfxPreInitDialog和_AfxPostInitDialog。它们主要是用来让对话框自动居中的。对话框的风格DS_CENTER, DS_CENTERMOUSE都是表示居中。另外,对话框资源编辑属性中的X Pos和Y Pos可指定对话框左上角的位置。
在该方法中,分为下面的步骤:
1. 调用基类(CWnd)的PreTranslateMessage方法
2. 如果未处理,判断是否为Shift + F1的帮助模式,如果是,那么返回FALSE,表示还需要消息还将进一步处理
3. 如果焦点在一个多行的CEdit控件上,且用户按下了ESC键,那么如果没有Cancel按钮,或者该按钮被Disable了,那么显示发送一个Cancel消息到本对话框,以便能关闭该对话框
4. 进入PreTranslateInput方法。该方法只处理键盘和鼠标消息,且将消息传递给IsDialogMessage方法。这是对话框中处理键盘和鼠标消息的入口。
DoDataExchange是CWnd提供的虚方法,CDialog类实现了它。该方法用来将控件和成员变量绑定,之后,代码中使用UpdateData方法来进行正向和反向的转换。
除此之外,为了方便绑定,MFC提供了许多的DDX前缀的宏(X表示Exchange),比如:DDX_Text, DDX_Check, DDX_Radio, DDX_Control等。它们的定义位于afxdd_.h中。
对于控件数值的校验,MFC提供了DDV前缀的宏。(V表示Validation)
DDX_Control往往用来在对话框中定义某个控件对象,可以是标准的,也可以是派生的。该宏主要完成了一个subclass的过程(调用SubclassWindow)。CWnd类也提供了SubclassDlgItem方法,控件可以使用它来完成subclass。我们经常可以在PreSubclassWindow虚方法中来完成这些任务。当然在OnInitDialog中也可以。
该方法首先判断一个消息是否是对话框消息,如果是,那么将处理它,即:将其转换成相应的消息,然后将其派发到窗口。这里主要是处理键盘消息。比较典型的有两种键盘操作:TAB键和Down/Up键,它们被用来在对话框的控件之间进行导航。该方法将这些键盘消息转换成相应的控件消息,并发送到该对应控件。
该方法在转换消息时,将发送WM_GETDLGCODE消息,询问控件的处理类型。系统提供了如下处理代码,控件可以重载OnGetDlgCode方法,返回需要的类型:
Value |
Meaning |
DLGC_BUTTON |
Button. |
DLGC_DEFPUSHBUTTON |
Default push button. |
DLGC_HASSETSEL |
EM_SETSEL messages. |
DLGC_RADIOBUTTON |
Radio button. |
DLGC_STATIC |
Static control. |
DLGC_UNDEFPUSHBUTTON |
Non-default push button. |
DLGC_WANTALLKEYS |
All keyboard input. |
DLGC_WANTARROWS |
Direction keys. |
DLGC_WANTCHARS |
WM_CHAR messages. |
DLGC_WANTMESSAGE |
All keyboard input (the application passes this message in the MSG structure to the control). |
DLGC_WANTTAB |
TAB key. |
值得注意的是,该方法并不只适用于对话框,它适用于所有包含控件的窗口。
基本步骤如下,直到下面有一个地方处理消息:
1. 调用基类CWnd的OnCmdMsg方法
2. 调用父窗口的OnCmdMsg方法
3. 调用线程对象(CWinThread)的OnCmdMsg方法
关于该虚方法的详细解释,请参见《窗口消息》中的“MFC的消息分发”一节。