
3.6 对话框的窗口消息
第1章提到,所谓消息(Message)就是用于描述某个事件所发生的信息,而事件(Event)则是用户操作应用程序产生的动作(比如用户按下鼠标左键)或Windows系统自身所产生的动作(比如某个时间点到了)。事件和消息两者密切相关,事件是原因,消息是结果,事件产生消息,消息对应事件。在MFC应用程序中传输的消息有三种类型:窗口消息、命令消息和控件通知。窗口消息(Window Message)一般与窗口的内部运作有关,如创建窗口、绘制窗口和销毁窗口等。通常,消息是从系统发送到窗口,或从窗口发送到窗口。
对话框的窗口消息很多,常用的消息如下。
● WM_CREATE消息:建立对话框的时候将发送该消息。
● WM_ACTIVE消息:对话框激活(获得焦点)的时候将发送该消息。
● WM_SHOWWINDOW:对话框窗口显示或隐藏的时候发送该消息。
● WM_PAINT:对话框窗口框架需要绘制的时候发送该消息。
● WM_SIZING:用户正在重新调整窗口的大小时发送该消息。
● WM_DESTROY:窗口即将销毁时发送该消息。
● WM_CLOSE:发出信号(比如单击关闭按钮或对话框系统菜单上的关闭菜单项)要关闭窗口时发送该消息。
在处理WM_CREATE消息的时候,对话框还没有被显示在屏幕上,而且对话框中的控件都还没有被创建,因此不能在它对应的消息处理函数OnCreate中使用控件。如果要对控件进行初始化,通常是在对话框类的虚函数OnInitDialog中进行。
这里要注意下WM_DESTROY和WM_CLOSE的发送是有次序的,介绍下Windows应用程序的完整退出过程,用户单击窗口右上角的关闭按钮,发送WM_CLOSE消息,此消息处理中调用DestroyWindow函数,发送WM_DESTROY消息。此消息处理中调用PostQuitMessage函数,发送WM_QUIT消息到消息队列中。GetMessage捕获到WM_QUIT,返回0,退出消息循环(应用程序真正退出,就是从任务管理器中看不到该进程了)。要注意只有关闭了消息循环,应用程序的进程才真正退出(在任务管理器里消失)。其中,WM_QUIT是个非窗口下消息,用它来关闭消息循环。WM_DESTROY消息在是窗口正在关闭时发出的,当得到WM_DESTROY消息的时候,窗口已经从视觉上被删除了。GetMessage表示从消息队列中获取一个消息。
因此,WM_CLOSE消息发出的时候,用户可以根据自己的意愿来选择到底是否关闭窗口,WM_DESTORY发出的时候窗口肯定在关闭了。WM_QUIT发出的时候,则进程肯定在退出了。一个主窗口被关闭,并不意味着应用程序结束了,它可以在没有窗口的条件下继续运行。
3.6.1 为对话框添加消息处理函数
上面讲了对话框常用的几个消息,本小节通过一个例子来说明如何为对话框添加消息处理函数。在这个例子中,可以看到WM_CLOSE和WM_DESTROY的触发条件是不同的。
【例3.6】 为对话框添加消息处理函数
(1)打开Visual C++ 2013,新建一个对话框工程,工程名是Test。
(2)切换到资源视图,打开对话框资源编辑界面,然后在对话框上右击,在右击菜单上选择“属性”来打开属性视图。在属性视图上,单击“消息”小按钮,切换到“消息”页面,如图333所示。
此时,在图3-33的CTestDlg下可以看到很多WM_开头的消息了,这些消息都是对话框这个窗口的消息。现在来添加WM_CREATE的消息处理函数,在左边一列找到WM_CREATE消息,然后单击右边一格末尾处的小箭头按钮,然后再选择OnCreate,如图3-34所示。

图3-33

图3-34
接着,系统自动出现编辑界面,并且自动定位到消息处理函数,然后我们在该函数中添加代码,弹出一个消息对话框。代码如下:
int CTestDlg::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CDialogEx::OnCreate(lpCreateStruct) == -1) return -1; // TODO: 在此添加您专用的创建代码 AfxMessageBox(_T("我是消息对话框,马上出现主对话框。")); return 0; }
(3)再响应WM_CLOSE,在里面我们询问用户是否退出程序。添加WM_CLOSE消息处理函数后,代码如下:
void CTestDlg::OnClose() { // TODO: 在此添加消息处理程序代码和/或调用默认值 if (AfxMessageBox(_T("确定要关闭程序吗?"), MB_YESNO) == IDYES) CDialogEx::OnClose(); }
其中,在CDialogEx::OnClose();中会调用DestroyWindow函数,DestroyWindow函数里面会发生WM_DESTROY消息。另外,要单击主对话框右上角的关闭按钮才会发生WM_CLOSE。
(4)我们再来处理WM_DESTROY消息,当WM_DESTROY发送出来后,主对话框窗口肯定关闭了,但我们在这个消息的处理函数中弹出一个消息对话框来,此时任务管理器中还能看到进程的名字,这说明主对话框虽然关闭了,但并不意味着进程已经退出了。添加WM_DESTROY消息处理函数后,代码如下:
void CTestDlg::OnDestroy() { CDialogEx::OnDestroy(); // TODO: 在此处添加消息处理程序代码 AfxMessageBox(_T("我是消息对话框\r这个时候,主对话框已经在屏幕上消失了\r但任务管理器中的进程还在")); }
(5)保持工程并运行,运行结果如图3-35所示。
单击“是”按钮,出现如图3-36所示的消息对话框。

图3-35

图3-36
这个例子主要为了说明对话框程序退出的时候发送出来的各个消息的前后次序。
3.6.2 为对话框添加自定义消息
上面添加的消息是系统预定义的消息,此外,我们还可以为对话框添加自己定义的消息,由于是自定义的消息,因此添加的方式必须以手工的方式来添加。定义了自定义消息后,发送这个消息可以通过窗口类的成员函数CWnd::SendMessage或CWnd::PostMessage来实现,SendMessage函数的原型如下:
LRESULT SendMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );
其中,message表示要发送的消息;wParam表示与消息有关的信息;lParam表示其他消息相关的信息。LRESULT、WPARAM和LPARAM都是系统预定义的类型,其实都是长整型,它们的定义如下:
typedef UINT_PTR WPARAM; typedef LONG_PTR LPARAM; typedef LONG_PTR LRESULT;
CWnd:: PostMessage函数的原型如下:
BOOL PostMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );
参数含义和SendMessage相同。
因为对话框类CDialog是继承于窗口类CWnd的,所以在对话框中可以直接使用这2个函数。虽然这2个函数都是发送消息,但它们是有区别的。SendMessage函数发送出消息后,该函数SendMessag不会立即返回,而是要等到发出消息所对应的消息处理函数执行完毕后才能返回,继续执行SendMessage后面的代码;PostMessage发出消息后立即返回,马上执行PostMessage后面的代码,而不会去管消息处理函数是否执行完毕。我们可以通过下面的例子来体会这种差别。
【例3.7】 为对话框添加自定义消息
(1)打开Visual C++ 2013,新建一个对话框工程,工程名是Test。
(2)切换到资源视图打开对话框资源,删除对话框上所有控件,并添加2个按钮,设置按钮的标题分别为“用SendMessage发送自定义消息”和“用PostMessage发送自定义消息”。
(3)切换到解决方案管理器,打开TestDlg.h,在开头添加自定义消息的宏定义:
#define WM_MYMSG WM_USER+101//定义一个自定义消息
其中,WM_USER是系统预定义的宏,我们自己定义的消息必须从WM_USER开始。这里加101也可以改为加其他正整数,但微软推荐自定义消息值最好至少为WM_USER + 100。
(4)我们定义了一个消息,肯定要有这个消息的处理函数,现在我们来添加消息的处理函数,因为是自定义消息,所以只能在源码中直接添加。消息处理函数就是一个类的成员函数,打开TestDlg.h,在类CTestDlg里的DECLARE_MESSAGE_MAP()前面添加一个函数声明:
afx_msg LRESULT OnMyMsg (WPARAM wParam, LPARAM lParam);
其中,afx_msg是一个宏,但它是空定义,仅仅表示声明的函数是一个消息响应函数。消息处理函数的说明前一般都有afx_msg的前缀,用于把消息处理函数与其他的窗口成员函数进行区分。
打开TestDlg.cpp,在文件末尾添加DoMyMsg函数的实现代码:
LRESULT CTestDlg::OnMyMsg(WPARAM wParam, LPARAM lParam) { AfxMessageBox((CString)(char*)wParam); return 0; }
该段代码的含义是把收到字符串显示出来,要注意类型转换,先转换成char*,再转换成CString类型,CString是MFC库中的字符串类。
再定位到END_MESSAGE_MAP()处,在它上面一行添加:
ON_MESSAGE(WM_MYMSG, OnMyMsg)
其中,ON_MESSAGE是处理自定义消息的宏,它把我们定义的消息WM_MYMSG和我们定义的消息处理函数OnMyMsg关联起来了,就是让系统知道消息WM_MYMSG对应消息处理函数是OnMyMsg。
至此,自定义消息及其处理函数添加完毕了。下面我们来发送这个消息。
(5)切换到资源视图,打开对话框,对上面名为“发送自定义消息”的按钮上双击来添加按钮的消息处理函数,代码如下:
void CTestDlg::OnBnClickedButton1() //SendMessage方式 { // TODO: 在此添加控件通知处理程序代码 char szText[] = "Visual C++ 2013开发工具"; SendMessage(WM_MYMSG, (UINT)szText, 0); }
其中,SendMessage是发送消息的函数,第一个参数就是我们要定义的消息,第二个参数目前是个字符数组的地址,把这个地址值传到消息处理函数中去。
再为另外一个按钮添加处理函数,代码如下:
void CTestDlg::OnBnClickedButton2() //PostMessage方式 { // TODO: 在此添加控件通知处理程序代码 char szText[] = "Visual C++ 2013开发工具"; PostMessage(WM_MYMSG, (UINT)szText, 0); }
(6)保存工程并运行,我们分别单击2个按钮,会发现SendMessage发出消息后弹出的消息框上的字符串就是我们参数里面所传的,而PostMessage发出消息后弹出的消息框上为空。这是因为szText是局部变量,它所拥有字符串"Visual C++ 2013开发工具"在函数OnBnClickedButton1或OnBnClickedButton2执行完毕后会自动释放,SendMessage会等到消息处理函数执行完毕后才返回,所以在消息处理函数中,szText所指向的字符串还存在,因此可以在消息处理函数中打印正确的内容。如图3-37所示。
而PostMessage发出消息后立即返回,OnBnClickedButton2马上执行完毕导致OnBnClickedButton2中szText马上释放了,因此在消息处理函数中已经无法获得szText所指的字符串了。运行结果如图3-38所示。

图3-37

图3-38