1. 程式人生 > >非模態對話方塊 建立 銷燬

非模態對話方塊 建立 銷燬

非模態對話方塊

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程式中的登入資料對話方塊改成非模態對話方塊。這樣做的好處在於如果使用者在輸入資料時發現編輯檢視中有錯誤的資料,那麼不必關閉對話方塊,就可以在編輯檢視中進行修改。

請讀者按下面幾步操作:

在登入資料對話方塊模板的屬性對話方塊的More Styles頁中選擇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後選擇Message Maps頁,並在Class name欄中選擇CRegisterDialog。然後,在Object IDs欄中選擇IDCANCEL後,在Messages欄中雙擊BN_CLICKED,這就建立了OnCancel。要建立PostNcDestroy,先在Object IDs欄中選擇CRegisterDialog,再在Messages欄中雙擊PostNcDestroy即可。

分別按清單5.10和5.11,對CRegisterView類和CRegisterDialog類進行修改。

清單5.10 CRegisterView類的部分程式碼

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.11 CRegisterDialog的部分程式碼

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( UINT nIDTemplate, 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刪除每一個所屬視窗,故只要非模態對話方塊是自動清除的,程式設計師就不必擔心對話方塊物件的刪除問題.