5.4.1非模態對話框的特點
與模態對話框不同,非模態對話框不壟斷用戶的輸入,用戶打開非模態對話框后,仍然可以與其它界面進行交互。
非模態對話框的設計與模態對話框基本類似,也包括設計對話框模板和設計CDialog類的派生類兩部分。但是,在對話框的創建和刪除過程中,非模態對話框與模態對話框相比有下列不同之處:
非模態對話框的模板必須具有Visible風格,否則對話框將不可見,而模態對話框則無需設置該項風格。更保險的辦法是調用CWnd::ShowWindow(SW_SHOW)來顯示對話框,而不管對話框是否具有Visible風格。
非模態對話框對象是用new操作符在堆中動態創建的,而不是以成員變量的形式嵌入到別的對象中或以局部變量的形式構建在堆棧上。通常應在對話框的擁有者窗口類內聲明一個指向對話框類的指針成員變量,通過該指針可訪問對話框對象。
通過調用CDialog::Create函數來啟動對話框,而不是CDialog::DoModal,這是模態對話框的關鍵所在。由于Create函數
不會啟動新的消息循環,對話框與應用程序共用同一個消息循環,這樣對話框就不會壟斷用戶的輸入。Create在顯示了對話框后就立即返回,而
DoModal是在對話框被關閉后才返回的。眾所周知,在MFC程序中,窗口對象的生存期應長于對應的窗口,也就是說,不能在未關閉屏幕上窗口的情況下先
把對應的窗口對象刪除掉。由于在Create返回后,不能確定對話框是否已關閉,這樣也就無法確定對話框對象的生存期,因此只好在堆中構建對話框對象,而
不能以局部變量的形式來構建之。
必須調用CWnd::DestroyWindow而不是CDialog::EndDialog來關閉
非模態對話框。調用CWnd::DestroyWindow是直接刪除窗口的一般方法。由于缺省的CDialog::OnOK和CDialog::
OnCancel函數均調用EndDialog,故程序員必須編寫自己的OnOK和OnCancel函數并且在函數中調用DestroyWindow來關
閉對話框。
因為是用new操作符構建非模態對話框對象,因此必須在對話框關閉后,用delete操作符刪除對話框對象。在屏幕上一個窗口被刪除后,框架會調用CWnd::PostNcDestroy,這是一個虛擬函數,程序可以在該函數中完成刪除窗口對象的工作,具體代碼如下
void CModelessDialog::PostNcDestroy
{
delete this;//刪除對象本身
}
這樣,在刪除屏幕上的對話框后,對話框對象將被自動刪除。擁有者對象就不必顯式的調用delete來刪除對話框對象了。
必須有一個標志表明非模態對話框是否是打開的。這樣做的原因是用戶有可能在打開一個模態對話框的情況下,又一次選擇打開命令。程序根據標志來決定是打開
一個新的對話框,還是僅僅把原來打開的對話框激活。通常可以用擁有者窗口中的指向對話框對象的指針作為這種標志,當對話框關閉時,給該指針賦NULL值,
以表明對話框對象已不存在了。
提示:在C++編程中,判斷一個位于堆中的對象是否存在的常用方法是判斷指向該對象的指針是否為空。這種機制要求程序員將指向該對象的指針初始化為NULL值,在創建對象時將返回的地址賦給該指針,而在刪除對象時將該指針置成NULL值。
根據上面的分析,我們很容易把Register程序中的登錄數據對話框改成非模態對話框。這樣做的好處在于如果用戶在輸入數據時發現編輯視圖中有錯誤的數據,那么不必關閉對話框,就可以在編輯視圖中進行修改。
請讀者按下面幾步操作:
在登錄數據對話框模板的屬性對話框的MoreStyles頁中選擇Visible項。
在RegisterView.h頭文件的CRegisterView類的定義中加入
public:
CRegisterDialog * m_pRegisterDlg;
在RegisterView.h頭文件的頭部加入對CRegisterDialog類的聲明
class CRegisterDialog;
加入該行的原因是在CRegisterView類中有一個CRegisterDialog類型的指針,因此必須保證CRegisterDialog類的
聲明出現在CRegisterView之前,否則編譯時將會出錯。解決這個問題有兩種辦法,一種辦法是保證在#include
“RegisterView.h”語句之前有#include“RegisterDialog.h”語句,這種辦法造成了一種依賴關系,增加了編譯負擔,
不是很好;另一種辦法是在CRegisterView類的聲明之前加上一個對CRegisterDialog的聲明來暫時“蒙蔽”編譯器,這樣在有
#include“RegisterView.h”語句的模塊中,除非要用到CRegisterDialog類,否則不用加入#include
“RegisterDialog.h”語句。
在RegisterDialog.cpp文件的頭部的#include語句區的末尾添加下面兩行
#include "RegisterDoc.h"
#include "RegisterView.h"
利用ClassWizard為CRegisterDialog類加入OnCancel和PostNcDestroy成員函數。加入的方法是進入
ClassWizard后選擇MessageMaps頁,并在Classname欄中選擇CRegisterDialog。然后,在ObjectIDs欄
中選擇IDCANCEL后,在Messages欄中雙擊BN_CLICKED,這就創建了OnCancel。要創建PostNcDestroy,先在
ObjectIDs欄中選擇CRegisterDialog,再在Messages欄中雙擊PostNcDestroy即可。
分別按清單5.10和5.11,對CRegisterView類和CRegisterDialog類進行修改。
清單5.10CRegisterView類的部分代碼
CRegisterView::CRegisterView()
{
//TODO:add construction code here
m_pRegisterDlg=NULL;//指針初始化為NULL
}
void CRegisterView::OnEditRegister()
{
//TODO:Add your command handler code here
if(m_pRegisterDlg)
m_pRegisterDlg->SetActiveWindow();//激活對話框
else
{
//創建非模態對話框
m_pRegisterDlg=new CRegisterDialog(this);
m_pRegisterDlg->Create(IDD_REGISTER,this);
}
}
清單5.11CRegisterDialog的部分代碼
void CRegisterDialog::PostNcDestroy()
{
//TODO:Add your specialized code here and/or call the base class
delete this;//刪除對話框對象
}
void CRegisterDialog::OnCancel()
{
//TODO:Add extra cleanup here
((CRegisterView*)m_pParent)->m_pRegisterDlg=NULL;
DestroyWindow();//刪除對話框
}
CRegisterView::OnEditRegister函數判斷登錄數據對話框是否已打開,若是,就激活對話框,否則,就創建該對話框。該函數中主要調用了下列函數:
調用CWnd::SetActiveWindow激活對話框,該函數的聲明為
CWnd* SetActiveWindow();
該函數使本窗口成為活動窗口,并返回原來活動的窗口。
調用CDialog::Create來顯示對話框,該函數的聲明為
BOOL Create(UINTnIDTemplate,CWnd*pParentWnd=NULL);
參數nIDTemplate是對話框模板的ID。pParentWnd指定了對話框的父窗口或擁有者。
當用戶在登錄數據對話框中點擊“取消”按鈕后,CRegisterDialog::OnCancel將被調用,在該函數中調用CWnd::
DestroyWindow來關閉對話框,并且將CRegisterView的成員m_pRegisterDlg置為NULL以表明對話框被關閉了。調用
DestroyWindow導致了對CRegisterDialog::PostNcDestroy的調用,在該函數中用delete操作符刪除了
CRegisterDialog對象本身。
編譯并運行Register,現在登錄數據對話框已經變成一個非模態對話框了。
5.4.2窗口對象的自動清除
一個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
來刪除對話框及其控件。
窗口對象本身的刪除則根據對象創建方式的不同,分為兩種情況。在MFC編程中,會使用大量的窗口對象,有些窗
口對象以變量的形式嵌入在別的對象內或以局部變量的形式創建在堆棧上,有些則用new操作符創建在堆中。對于一個以變量形式創建的窗口對象,程序員不必關
心它的刪除問題,因為該對象的生命期總是有限的,若該對象是某個對象的成員變量,它會隨著父對象的消失而消失,若該對象是一個局部變量,那么它會在函數返
回時被清除。
對于一個在堆中動態創建的窗口對象,其生命期卻是任意長的。初學者在學習C++編程時,對new操作符的使用往往不太踏
實,因為用new在堆中創建對象,就不能忘記用delete刪除對象。讀者在學習MFC的例程時,可能會產生這樣的疑問,為什么有些程序用new創建了一
個窗口對象,卻未顯式的用delete來刪除它呢?問題的答案就是有些MFC窗口對象具有自動清除的功能。
如前面講述非模態對話框時
所提到的,當調用CWnd::DestroyWindow或::DestroyWindow刪除一個窗口時,被刪除窗口的PostNcDestroy成員
函數會被調用。缺省的PostNcDestroy什么也不干,但有些MFC窗口類會覆蓋該函數并在新版本的PostNcDestroy中調用
delete
this來刪除對象,從而具有了自動清除的功能。此類窗口對象通常是用new操作符創建在堆中的,但程序員不必操心用delete操作符去刪
除它們,因為一旦調用DestroyWindow刪除窗口,對應的窗口對象也會緊接著被刪除。
不具有自動清除功能的窗口類如下所示。這些窗口對象通常是以變量的形式創建的,無需自動清除功能。
所有標準的Windows控件類。
從CWnd類直接派生出來的子窗口對象(如用戶定制的控件)。
切分窗口類CSplitterWnd。
缺省的控制條類(包括工具條、狀態條和對話條)。
模態對話框類。
具有自動清除功能的窗口類如下所示,這些窗口對象通常是在堆中創建的。
主框架窗口類(直接或間接從CFrameWnd類派生)。
視圖類(直接或間接從CView類派生)。
讀者在設計自己的派生窗口類時,可根據窗口對象的創建方法來決定是否將窗口類設計成可以自動清除的。例如,對于一個非模態對話框來說,其對象是創建在堆中的,因此應該具有自動清除功能。
綜上所述,對于MFC窗口類及其派生類來說,在程序中一般不必顯式刪除窗口對象。也就是說,既不必調用DestroyWindow來刪除窗口對象封裝的
窗口,也不必顯式地用delete操作符來刪除窗口對象本身。只要保證非自動清除的窗口對象是以變量的形式創建的,自動清除的窗口對象是在堆中創建的,
MFC的運行機制就可以保證窗口對象的徹底刪除。
如果需要手工刪除窗口對象,則應該先調用相應的函數(如CWnd::
DestroyWindow)刪除窗口,然后再刪除窗口對象.對于以變量形式創建的窗口對象,窗口對象的刪除是框架自動完成的.對于在堆中動態創建了的非
自動清除的窗口對象,必須在窗口被刪除后,顯式地調用delete來刪除對象(一般在擁有者或父窗口的析構函數中進行).對于具有自動清除功能的窗口對
象,只需調用CWnd::DestroyWindow即可刪除窗口和窗口對象。注意,對于在堆中創建的窗口對象,不要在窗口還未關閉的情況下就用
delete操作符來刪除窗口對象.
提示:在非模態對話框的OnCancel函數中可以不調用CWnd::
DestroyWindow,取而代之的是調用CWnd::ShowWindow(SW_HIDE)來隱藏對話框.在下次打開對話框時就不必調用
Create了,只需調用CWnd::ShowWindow(SW_SHOW)來顯示對話框.這樣做的好處在于對話框中的數據可以保存下來,供以后使用.
由于擁有者窗口在被關閉時會調用DestroyWindow刪除每一個所屬窗口,故只要非模態對話框是自動清除的,程序員就不必擔心對話框對象的刪除問
題.