今天项目中遇到的问题,记录下来,做个总结。
一个简单的目的是创建一个非模态对话框并在对话框关闭后将其销毁。
这里的销毁包括:销毁对话框对象资源和对话框对象指针;
首先说创建对话框:
一、模态对话框(model dialog box)
在程序运行的过程中,若出现了模态对话框,那么主窗口将无法发送消息,直到模态对话框退出才可以发送。
点击模态对话框中的OK按钮,模态对话框会被销毁。创建一个模态对话框的代码:
- //创建一个模态对话框
- CTestDialog td;
- td.DoModal();
因为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文件,在你要创建对话框的位置添加如下代码:
- //采用成员变量创建一个非模态对话框
- pTD = new CTestDialog(); //给指针分配内存
- pTD->Create(IDD_DIALOG1); //创建一个非模态对话框
- pTD->ShowWindow(SW_SHOWNORMAL); //显示非模态对话框
最后在所在类的析构函数中收回pTD所指向的内存:
- 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.On
4.On
5.On
6.On
7.On
8.On
9.On
MFC应用程序关闭窗口的顺序(非模态窗口)
1.On
2.On
3.On
4.PostNcDestroy() 重载函数,作为处理On
对于非模态窗口,必须重载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()没有调用。
模态的对话框可以用EndDialog来销毁, 非模态的对话框要用DestroyWindow来销毁
MFC应用程序中创建模态对话框的函数调用顺序:
1.DoModal() 重载函数,重载DoModal()成员函数
2.PreSubclassWindow() 重载函数,允许首先子分类一个窗口
3.On
4.On
5.On
6.On
7.On
8.On
9.On
10. On
MFC应用程序中关闭模式对话框的顺序
1.On
2.On
3.On
4.On
5.PostNcDestroy() 重载函数,作为处理On
{
}
通常这样是重载虚函数PostNcDestroy()来实现
void CMyDialog::PostNcDestroy()
{
}