今天项目中遇到的问题,记录下来,做个总结。
一个简单的目的是创建一个非模态对话框并在对话框关闭后将其销毁。
这里的销毁包括:销毁对话框对象资源和对话框对象指针;
首先说创建对话框:
一、模态对话框(model dialog box)
在程序运行的过程中,若出现了模态对话框,那么主窗口将无法发送消息,直到模态对话框退出才可以发送。
点击模态对话框中的OK按钮,模态对话框会被销毁。创建一个模态对话框的代码:
//创建一个模态对话框
CTestDialog td;
td.DoModal();
其中CTestDialog为所要创建的对话框类。而这个对话框是在另个类中创建。
因为DoModal()函数的一个功能是,当前只能运行此模态对话框,且停止主窗口的运行,直到模态对话框退出,才允许主窗口运行。
DoModal()函数也有显示对话框的功能,所以也无需调用其他函数来显示对话框。(这里就关系到DoModal中的函数调用顺序,后面会有顺序的说明)
二、非模态对话框(modaless dialog box)
创建非模态对话框,必须声明一个指向CTestDialog类的指针变量,且需要显示的调用ShowWindow()才能将对话框显示出来。有两种创建方法:
(1)采用局部变量创建一个非模态对话框
//采用局部变量创建一个非模态对话框
CTestDialog *pTD = new CTestDialog();
pTD->Create(IDD_DIALOG1); //创建一个非模态对话框
pTD->ShowWindow(SW_SHOWNORMAL); //显示非模态对话框 其中参数用swp_SHOWNOMAL, SW_SHOW, SW_VISION 好像效果是一样的
这种方式创建的对话框,如果对话框对象指针是局部的,那么离开作用域,指针就消失掉,所以当释放对话框对象指针所指的内存时就无法在创建对话框的类中进行释放,若不做处理,则会造成内存泄露。打开很多个对话框则会导致内存耗尽程序奔溃。
可以有两种解决办法:
1) 在创建对话框的类中,将对话框对象指针存储起来,然后释放对应指针,一般可以定义一个全局的或者成员变量来存储(用map)
2)将对话框对象指针的释放交给对话框自身去维护,在上层类中之创建出来即可。具体方法是:在对话框类中重写PostNcDestory方法,该方法中delete this即可。具体顺序后面介绍。
(2)采用成员变量创建一个非模态对话框
首先在你所要编写的类的头文件中声明一个指针变量:
private:
CTestDialog *pTD;
然后再在相应的CPP文件,在你要创建对话框的位置添加如下代码:
[cpp] view plaincopy
//采用成员变量创建一个非模态对话框
pTD = new CTestDialog(); //给指针分配内存
pTD->Create(IDD_DIALOG1); //创建一个非模态对话框
pTD->ShowWindow(SW_SHOWNORMAL); //显示非模态对话框
最后在所在类的析构函数中收回pTD所指向的内存:
[cpp] view plaincopy
delete pTD;
但这样的方法只能创建一个对话框,若是再打开一个对话框,则之前的指针丢失(会造成内存不好释放)。
若要用这种方式创建多个对话框可以用一个map管理存储指针,释放时对应释放,关闭了哪个对话框就把那个对应指针释放掉,但这样做很麻烦。
我的做法是第一种方法,在对话框类中维护释放和关闭对话框。下面会将方法写出来。
一个非模态的MFC 窗口的销毁过程:
假设自己通过new创建了一个窗口对象pWnd,然后pWnd->Create。则销毁窗口的调用次序:
1. 手工调用pWnd->DestroyWindow(); // 一般在对话框类中的OnCancle函数中postMessage(WM_DESTORY)或者直接调用
2. DestroyWindow会发送WM_DESTROY;
3. WM_DESTROY对应的消息处理函数是OnDestroy();
4. DestroyWindow会发送WM_NCDESTROY;
5. WM_NCDESTROY对应的消息处理函数是OnNcDestroy;
6. OnNcDestroy最后会调用PostNcDestroy;
7. PostNcDestroy经常被用户重载以提供释放内存操作。例如可以使用delete this;
通过这种方式,窗口对象对应的窗口和窗口对象本身都被释放了。
注: 销毁窗口对象对应的窗口和释放窗口对象指针 ,可以通过DestroyWindow,这是比较好的方法,因为最后MFC会自动相应WM_CLOSE导致CframWnd::DestroyWindow被调用,然后会一次释放所有子窗口的句柄。用户需要做的是在PostNcDestroy中释放堆窗口对象指针。但因为某些对象是在栈中申请的,所以delete this可能出错。这就要保证写程序时自己创建的窗口尽量使用堆申请。
一个MFC窗口对象包括两方面的内容:一是窗口对象封装的窗口,即存放在m_hWnd成员中的HWND(窗口句柄),二是窗口对象本身是一个C++对象。要删除一个MFC窗口对象,应该先删除窗口对象封装的窗口,然后删除窗口对象本身。
删除窗口最直接方法是调用CWnd::DestroyWindow或::DestroyWindow,前者封装了后者的功能。前者不仅会调用后者,而且会使成员m_hWnd保存的HWND无效(NULL)。如果DestroyWindow删除的是一个父窗口或拥有者窗口,则该函数会先自动删除所有的子窗口或被拥有者,然后再删除父窗口或拥有者。在一般情况下,在程序中不必直接调用DestroyWindow来删除窗口,因为MFC会自动调用DestroyWindow来删除窗口。例如,当用户退出应用程序时,会产生WM_CLOSE消息,该消息会导致MFC自动调用CWnd::DestroyWindow来删除主框架窗口,当用户在对话框内按了OK或Cancel按钮时,MFC会自动调用CWnd::DestroyWindow来删除对话框及其控件。
对于一个在堆中动态创建的窗口对象,其生命期却是任意长的。所以可能会产生这样的疑问,为什么有些程序用new创建了一个窗口对象,却未显式的用delete来删除它呢?问题的答案就是有些MFC窗口对象具有自动清除的功能。
如前面讲述非模态对话框时所提到的,当调用CWnd::DestroyWindow或::DestroyWindow删除一个窗口时,被删除窗口的PostNcDestroy成员函数会被调用。缺省的PostNcDestroy什么也不干,但有些MFC窗口类会覆盖该函数并在新版本的PostNcDestroy中调用delete this来删除对象,从而具有了自动清除的功能。此类窗口对象通常是用new操作符创建在堆中的,但程序员不必操心用delete操作符去删除它们,因为一旦调用DestroyWindow删除窗口,对应的窗口对象也会紧接着被删除。
对于在堆中动态创建了的非自动清除的窗口对象,必须在窗口被删除后,显式地调用delete来删除对象(一般在拥有者或父窗口的析构函数中进行).对于具有自动清除功能的窗口对象,只需调用CWnd::DestroyWindow即可删除窗口和窗口对象。注意,对于在堆中创建的窗口对象,不要在窗口还未关闭的情况下就用delete操作符来删除窗口对象。
下面总结一下MFC对话框创建和销毁的函数调用顺序:
非模态对话框
MFC应用程序创建窗口的过程
1.PreCreateWindow() 该函数是一个重载函数,在窗口被创建前,可以在该重载函数中改变创建参数 (可以设置窗口风格等等)
2.PreSubclassWindow() 这也是一个重载函数,允许首先子分类一个窗口
3.OnGetMinMaxInfo() 该函数为消息响应函数,响应的是WM_GETMINMAXINFO消息,允许设置窗口的最大或者最小尺寸
4.OnNcCreate() 该函数也是一个消息响应函数,响应WM_NCCREATE消息,发送消息以告诉窗口的客户区 即将被创建
5.OnNcCalcSize() 该函数也是消息响应函数,响应WM_NCCALCSIZE消息,作用是允许改变窗口客户区大小
6.OnCreate() 该函数也是一个消息响应函数,响应WM_CREATE消息,发送消息告诉一个窗口已经被创建
7.OnSize() 该函数也是一个消息响应函数,响应WM_SIZE消息,发送该消息以告诉该窗口大小已经 发生变化
8.OnMove() 消息响应函数,响应WM_MOVE消息,发送此消息说明窗口在移动
9.OnChildNotify() 该函数为重载函数,作为部分消息映射被调用,告诉父窗口即将被告知一个窗口刚刚被创建
MFC应用程序关闭窗口的顺序(非模态窗口)
1.OnClose() 消息响应函数,响应窗口的WM_CLOSE消息,当关闭按钮被单击的时候发送此消息
2.OnDestroy() 消息响应函数,响应窗口的WM_DESTROY消息,当一个窗口将被销毁时,发送此消息
3.OnNcDestroy() 消息响应函数,响应窗口的WM_NCDESTROY消息,当一个窗口被销毁后发送此消息
4.PostNcDestroy() 重载函数,作为处理OnNcDestroy()函数的最后动作,被CWnd调用
对于非模态窗口,必须重载OnCancel函数,在函数中调用DestroyWindows()方法,且不能调用基类的函数。因为基类函数中调用的是 EndDialog()方法。(因为EndDialog是关闭模态对话框时调用的)而OnClose()也会调用OnCancel()方法。另外想通过OnOK关闭对话框,也必须同样处理,不能直接用默认方法。
所以对于非模态窗口,其关闭过程为:
OnClose()->OnCancel()->DestroyWindow()->OnDestroy()->OnNcDestroy() ,->仅表示时间先后而已
而OnNcDestroy()最后又调用了PostNcDestroy()
说明:OnOK是对ID_OK的响应, OnCancel是对IDCANCEL的响应. 前者对应键盘的Enter, 后者对应Esc。
OnOK()和OnCancel()都调用了EndDialog().OnOK调用了UpdateData(TRUE)而OnCacel()没有调用。
在OnOK()结束刚进入DestroyWindow时,其实窗口并未关闭,依然可以用ShowWindow显示出来
模态的对话框可以用EndDialog来销毁, 非模态的对话框要用DestroyWindow来销毁
MFC应用程序中创建模态对话框的函数调用顺序:
1.DoModal() 重载函数,重载DoModal()成员函数
2.PreSubclassWindow() 重载函数,允许首先子分类一个窗口
3.OnCreate() 消息响应函数,响应WM_CREATE消息,发送此消息以告诉一个窗口已经被创建
4.OnSize() 消息响应函数,响应WM_SIZE消息,发送此消息以告诉窗口大小发生变化
5.OnMove() 消息响应函数,响应WM_MOVE消息,发送此消息,以告诉窗口正在移动
6.OnSetFont() 消息响应函数,响应WM_SETFONT消息,发送此消息,以允许改变对话框中控件的字体
7.OnInitDialog() 消息响应函数,响应WM_INITDIALOG消息,发送此消息以允许初始化对话框中的控件, 或者是创建新控件
8.OnShowWindow() 消息响应函数,响应WM_SHOWWINDOW消息,该函数被ShowWindow()函数调用
9.OnCtlColor() 消息响应函数,响应WM_CTLCOLOR消息,被父窗口发送已改变对话框或对话框上面控件的颜色
10. OnChildNotify() 重载函数,作为WM_CTLCOLOR消息的结果发送
MFC应用程序中关闭模式对话框的顺序
1.OnClose() 消息响应函数,响应WM_CLOSE消息,当"关闭"按钮被单击的时候,该函数被调用
2.OnKillFocus() 消息响应函数,响应WM_KILLFOCUS消息,当一个窗口即将失去键盘输入焦点以前被发送
3.OnDestroy() 消息响应函数,响应WM_DESTROY消息,当一个窗口即将被销毁时,被发送
4.OnNcDestroy() 消息响应函数,响应WM_NCDESTROY消息,当一个窗口被销毁以后被发送
5.PostNcDestroy() 重载函数,作为处理OnNcDestroy()函数的最后动作被CWnd调用
对于DoModal出来的窗口,可以使用默认的OnOk()和OnCancel()来处理。其基类方法中会调用EndDialog()方法。
最后注意一个问题,通常我们创建一个非模态窗口时,可能会这样写
{
CDialog * pWnd = new CMyDialog();
pWnd->Create(……);
pWnd->ShowWindow(SW_SHOW);
}
一般是在一个模块或者一个函数中创建窗口,但是却无法知道什么时候关闭窗口。而pWnd也只是作为一个局部变量。那么如何对它进行析构呢?
通常这样是重载虚函数PostNcDestroy()来实现
void CMyDialog::PostNcDestroy()
{
CDialog::PostNcDestroy();
delete this;
}
为什么把对话框类的delete this放在PostNcDestroy中而不是OnNcDestroy?
这是因为OnNcDestroy只被已建立的窗口调用。如果建立窗口失败(如PreCreateWindow), 则没有窗口处来发送