对话框经常被使用,因为对话框可以从模板创建,而对话框模板是可以使用资源编辑器方便地进行编辑的。
一,模式与非模式对话框
1.模式对话框
一个模式对话框是一个有系统菜单、标题栏、边线等的弹出式窗口。在创建对话框时指定WS_POPUP, WS_SYSMENU, WS_CAPTION和 DS_MODALFRAME风格。即使没有指定WS_VISIBLE风格,模
式对话框也会被显示。创建对话框窗口时,将发送WM_INITDIALOG消息(如果指定对话框的DS_SETFONT风格,还有WM_SETFONT消息)给对话框过程。
对话框窗口被创建之后,Windows使得它成为一个激活的窗口,它保持激活直到对话框过程调用::EndDialog函数结束对话框的运行或者Windows激活另一个应用程序为止,在激活时,用户
或者应用程序不可以激活它的所属窗口(Owner window)。从某个窗口创建一个模式对话框时,Windows自动地禁止使用(Disable)这个窗口和它的所有子窗口,直到该模式对话框被关闭和
销毁。虽然对话框过程可以Enable所属窗口,但是这样做就失去了模式对话框的作用,所以不鼓励这样做。
Windows创建模式对话框时,给当前捕获鼠标输入的窗口(如果有的话)发送消息WM_CANCLEMODE。收到该消息后,应用程序应该终止鼠标捕获(Release the mouse capture)以便于用户能
把鼠标移到模式对话框;否则由于Owner窗口被禁止,程序将失去鼠标输入。
为了处理模式对话框的消息,Windows开始对话框自身的消息循环,暂时控制整个应用程序的消息队列。如果Windows收到一个非对话框消息时,则它把消息派发给适当的窗口处理;如果收
到了WM_QUIT消息,则把该消息放回应用程序的消息队列里,这样应用程序的主消息循环最终能处理这个消息。
当应用程序的消息队列为空时,Windows发送WM_ENTERIDLE消息给Owner窗口。在对话框运行时,程序可以使用这个消息进行后台处理,当然应该注意经常让出控制给模式对话框,以便它能
接收用户输入。如果不希望模式对话框发送 WM_ENTERIDlE消息,则在创建模式对话框时指定DS_NOIDLEMSG风格。
一个应用程序通过调用::EndDialog函数来销毁一个模式对话框。一般情况下,当用户从系统菜单里选择了关闭(Close)命令或者按下了确认(OK)或取消(CANCLE)按钮,::EndDialog
被对话框过程所调用。调用::EndDialog时,指定其参数nResult的值,Windows将在销毁对话框窗口后返回这个值,一般,程序通过返回值判断对话框窗口是否完成了任务或者被用户取消。
2. 无模式对话框
一个无模式对话框是一个有系统菜单、标题栏、边线等的弹出式窗口。在创建对话框模板时指定WS_POPUP、WS_CAPTION、WS_BORDER和WS_SYSMENU风格。如果没有指定WS_VISIBLE风格,无
模式对话框不会自动地显示出来。一个无模式对话框既不会禁止所属窗口,也不会给它发送消息(WM_ENTERIDlE)。当创建一个无模式对话框时,Windows使它成为活动窗口,但用户或者程序
可以随时改变和设置活动窗口。如果对话框失去激活,那么即使所属窗口是活动的,在Z轴顺序上,它仍然在所属窗口之上。
应用程序负责获取和派发输入消息给对话框。大部分应用程序使用主消息循环来处理,但是为了用户可以使用键盘在控制窗口之间移动或者选择控制窗口,应用程序应该调
用::IsDialogMessage函数。
这里,顺便解释::IsDialogMessage函数。虽然该函数是为无模式对话框设计的,但是任何包含了控制子窗口的窗口都可以调用它,用来实现类似于对话框的键盘选择操作。
当::IsDialogMessage处理一个消息时,它检查键盘消息并把它们转换成相应对话框的选择命令。例如,当Tab 键被压下时,下一个或下一组控制被选中,当Down Arrow键按下后,一组控制中
的下一个控制被选择。::IsDialogMessage完成了所有必要的消息转换和消息派发,所以该函数处理的消息一定不要传递给TranslateMessage和DispatchMessage处理。一个无模式对话框不能
像模式对话框那样返回一个值给应用程序。但是对话框过程可以使用::SendMessage给所属窗口传递信息。
在应用程序结束之前,它必须销毁所有的无模式对话框。使用::DestroyWindow销毁一个无模式对话框,不是使用::EndDiaLog。一般来说,对话框过程响应用户输入,如用户选择了“取消
”按钮,则调用::DestroyWindow;如果用户没有有关动作,则应用程序必须调用::DestroyWindow。
二,对话框的MFC实现
1.在MFC中,对话框窗口的功能主要由CWnd和CDialog两个类实现。
2.模式对话框的实现
在Win32 SDK编程下的模式对话框使用了Windows提供给对话框窗口的窗口过程和自己的对话框过程,对话框过程将被窗口过程调用。但在MFC下,所有的窗口类都使用了同一个窗口过程,
CDialog也不例外。CDialog对象在创建Windows对话框时,采用了类似于CWnd的创建函数过程,采用子类化的手段将Windows提供给对话框的窗口过程取代为AfxWndProc或者AfxBaseWndProc,
同时提供了对话框过程AfxDlgProc。那么,这些“过程”是如何实现或者协调的呢?下文将予以分析。
I.MFC对话框过程
//MFC对话框过程AfxDlgProc的原型和实现如下:
由上可以看出,MFC的对话框函数AfxDlgProc仅处理消息WM_INITDIALOG,其他都留给对话框窗口过程处理。因此,它不同于SDK编程的对话框过程。程序员在SDK的对话框过程处理消息和事
件,实现自己的对话框功能。AfxDlgProc处理WM_INITDIALOG消息时调用虚拟函数OnInitDialog,给程序员一个机会处理对话框的初始化。
II.模式对话框窗口过程
AfxWndProc是所有的MFC窗口类使用的窗口过程,它取代了模式对话框原来的窗口过程(Windows提供),那么,MFC如何完成Win32下对话框窗口的功能呢?考查模式对话框的创建过程。
CDialog::DoModal用来创建模式对话框窗口并执行有关任务,和DoModal相关的是MFC内部使用的成员函数CDialog::PreModal和CDialog::PostModal。下面分别讨论它们的实现。
III.使用原对话框窗口过程作消息的缺省处理
对话框的消息处理过程和其他窗口并没有什么不同。这里主要分析的是如何把一些消息传递给对话框原窗口过程处理。下面,通过解释MFC对WM_INITDIALOG消息的处理来解释MFC窗口过程
和原对话框窗口过程的关系及其协调作用。MFC提供了WM_INITDIALOG消息的处理函数CDialog::HandleInitDialog,WM_INITDIALOG消息按照标准Windows的处理送给HandleInitDialog处理。
HandleInitDialog调用缺省处理过程Default,导致CWnd的Default函数被调用。CWnd::Default的实现如下:
顺便指出,从Default的实现可以看出线程状态的一个用途:它把本线程最新收到和处理的消息记录在成员变量m_lastSentMsg中。在Default的实现中,CWnd的DefWindowsProc被调用,其
实现如下:
综合上述分析,HandleInitDialog最终调用了窗口过程m_pfnSuper,即Windows提供给“对话框窗口类”的窗口过程,于是该窗口过程调用了对话框过程AfxDlgProc,导致虚拟函数
OnInitDialog被调用。OnInitDialog的MFC缺省实现主要完成三件事情:
调用ExecInitDialog初始化对话框中的控制;调用UpdateData初始化对话框控制中的数据;确定是否显示帮助按钮。所以,程序员覆盖该函数时,一定要调用基类的实现。
MFC采用子类化的方法取代了对话框的窗口过程,原来SDK下对话框过程要处理的东西大部分转移给MFC窗口过程处理,如处理控制窗口的控制通知消息等。如果不能处理或者必须借助于原来的
窗口过程的,则通过缺省处理函数Default传递给原来的窗口过程处理,如同这里对WM_INITDIALOG的处理一样。
IV.Dialog命令消息和控制通知消息的处理
通过覆盖CWnd的命令消息发送函数OnCmdMsg,CDialog实现了自己的命令消息发送路径
从上述实现可以看出,CDialog处理命令消息遵循如下顺序:对话框自身→父窗口→线程对象
例如,模式对话框产生的WM_ENTERIDLE消息就发送给父窗口处理。
CDialog::OnCmdMsg不仅适用于模式对话框,也适用于无模式对话框。
V.消息预处理和Dialog消息
另外,对话框窗口的消息处理还有一个特点,就是增加了对Dialog消息的处理,如同在介绍::IsDialogMessage函数时所述。如果是 Dialog消息,MFC框架将不会让它进入下一步的消息循
环。为此,MFC覆盖了CDialog的虚拟函数PreTranslateMessage,该函数的实现如下:
用到的函数:
根据IsDialogMessage(),对话框无法处理WM_KEYUP等消息,我们可以重载PreTranslateMessage()。PreTranslateMessage的实现不仅用于模式对话框,而且用于无模式对话框。
VI.模式对话框的消息循环
从DoModal的实现可以看出,DoModal调用CreateDlgIndirect创建的是无模式对话框,MFC如何来接管和控制应用程序的消息队列,实现一个模式对话框的功能呢?
CDialog调用了RunModalLoop来实现模式窗口的消息循环。RunModalLoop是CWnd的成员函数,它和相关函数的实现如下:
和CWinThread::Run的处理过程比较,RunModalLoop也分两个阶段进行处理。这里不同于Run的Idle处理,RunModalLoop是给父窗口发送WM_ENTERIDLE消息(如果需要的话);另外,当前对
话框的父窗口被Disabled,是不接收用户消息的。RunModalLoop是一个实现自己的消息循环的示例,消息循环的条件是模式化状态没有结束。实现线程自己的消息循环见8.5.6节。当用户按下
按钮“取消”、“确定”时,将导致RunModalLoop退出消息循环,结束对话框模式状态,并调用::EndDialog关闭窗口。
OnOk、OnCancle、EndDialog都可以用来关闭对话框窗口。其中:
OnOk首先进行数据交换,获取对话框中各个控制子窗口的数据,然后调用EndDialog结束对话框。
OnCancle直接EndDialog结束对话框。
三.数据交换
对话框数据交换指以下两种动作,或者是把内存数据写入对应的控制窗口,或者是从控制窗口读取数据并保存到内存变量中。MFC为了简化这些操作,以CDataExchange类和一些数据交换函
数为基础,提供了一套数据交换和校验的机制。
相应的DoDataExchange的实现如下:
DDX_ Control表示把IDC_NAME子窗口的内容传输到窗口m_name,或者相反。
DDX_ Text表示把IDC_AGE子窗口的内容按整数类型保存到m_nAge,或者相反。
DDV_MinMaxInt表示m_nAge应该在1和100之间取值
1.CDataExchange类
DoDataExchange类似于Serialize函数,CDataExchange类似于 CArchive。CDataExchange使用成员变量m_pDlgWnd保存要进行数据交换的对话框,使用成员变量 m_bSaveAndValidate指示数
据传输的方向,如果该变量真,则从控制窗口读取数据到成员变量,如果假,则从成员变量写数据到控制窗口。
在构造一个CDataExchange对象时,将保存有关信息在对象的成员变量中。构造函数如下:
2.数据交换和验证函数
在进行数据交换或者验证时,首先使用PrePareCtrl或者PrePareEditCtrl得到控制窗口的句柄,然后使用::GetWindowsText从控制窗口读取数据,或者使用::SetWindowsText写入数据到控制
窗口。下面讨论几个例子:
3.UpdateData函数
四,模式对话框的使用
CFormView是MFC使用无模式对话框的一个典型例子。CFormView是基于对话框模板创建的视,它的直接基类是CSrcollView,CSrcollView的直接基类才是CView。
这样不做详细介绍了。