一文搞懂如何創建基於對話框的模態對話框和非模態對話框

Windows應用程序工作的基本流程是從用戶那裏得到數據,經過相應的處理之後,再把處理結果輸出到屏幕、打印機或者其他的輸出設備上。那麼,應用程序是如何從用戶那裏得到數據,並且再將修改後的數據顯示給用戶的呢?這就需要用到 Windows應用程序中一個很重要的用戶接口 ——對話框


對話框其實就是一個窗口,不僅可以接收消息,而且還可以被移動和關閉,甚至可以在它的客戶區中進行繪圖。我們也可以將對話框看成是一個大容器,在它上面能夠放置各種各樣的標準控件和擴展控件,使程序支持用戶輸入的手段更加豐富。

常用控件介紹

控件 功能 控件類
靜態文本框(Static Text) 顯示文本, 一般不能接受輸入信息 CStatic
圖像控件(Picture) 顯式位圖、 圖標、 方框和圖元文件, 一般不能接受輸入信息 CStatic
編輯框(Edit Box) 輸入並編輯正文,支持單行和多行編輯 CEdit
按鈕(Button) 響應用戶的輸入,觸發相應的事件 CButton
單選按鈕(Radio Button) 用來從兩個或多個選項中選中一項 CButton
複選框(Check Box) 用作選擇標記,可以有選中、未選中和不確定三種狀態 CButton
組框(Group Box) 顯示正文和方框,主要用來將相關的一些控件(用千共同的目的)組織 在一起 CButton
列表框(List Box) 顯示一個列表,用戶可以從該列表中選擇一項或多項 CListBox
組合框(Combo Box) 是一個編輯框和一個列表框的組合。分爲簡易式、下拉式和下拉列表式 CComboBox
滾動條(Scroll Bar) 主要用來從一個預定義範圍值中迅速而有效地選取一個整數值 CScrollBar

對話框的種類

對話框分爲兩類: 模態(Modal)對話框和非模態(Modeless)對話框。

模態對話框

模態對話框是指當其顯示時,程序會暫停執行,直到關閉這個模態對話框後,才能繼續執行程序中其他任務。例如, 在Word中利用【文件/打開】菜單命令顯示一個 “打開 ”對話框後,再用鼠標去選擇其他菜單,或者進行該對話框以外的任何操作時,就會聽到那個聲音(大家都懂),這是因爲 “打開 “ 對話框是一個模態對話框。
模態對話框壟斷了用戶的輸入,當一個模態對話框打開時,用戶只能與該對話框進行交互,而其他用戶界面對象接收不到輸入信息。
我們平時所遇到的大部分對話框都是模態對話框。

非模態對話框

非模態對話框顯示時,允許轉而執行程序中其他任務,而不用關閉這個對話框。 典型的例子是Windows提供的記事本程序中的 “查找”對話框,該對話框不會壟斷用戶的輸入,打開“查找”對話框後,仍可以與其他用戶界面對象進行交互,用戶可以一邊查找,一邊修改文章,這樣,就大大方便了我們的使用,提高了效率。

對話框的新建和顯示

首先我們用VS2017新建一個基於對話框的MFC工程,並命名爲Dialog.建好項目之後,進行測試運行,會見到如下圖所示結果:
新建工程運行結果
如果想在程序中創建自己的對話框, 可以通過插入一個對話框資源來完成。 具體方法是:視圖->其他窗口->資源視圖(Ctrl + Shift+E)
在這裏插入圖片描述
結果如圖所示: VS2017自動將其標識設IDD_DIALOG1, 並添加到資源視圖選項卡中的 Dialog 項下,同時在資源編輯窗口中打開了這個新對話框資源,
在這裏插入圖片描述
可以看到,這個新建的 IDD_DIALOG1對話框中有兩個按鈕:確定取消, 並通過它們的屬性對話框可以發現 它們的ID分別爲IDOK和IDCANCEL。VS2017已經爲這兩個按鈕提供了默認的消息響應函數OnOK 和 OnCancel, 它們實現的主要功能都是樣的,就是關閉對話框,因此,當程序運行時,單擊這兩個按鈕中的仔何一個都可以關閉對話框。但是,單擊這兩個按鈕關閉對話框後,返回的結果值是不一樣的, 在程序中,通常根據該返回值來判斷用戶單擊的是哪個按鈕,從而確定用戶的行爲:是確定還是取消當前操作。
我們選中IDD_DIALOG1這個對話框資源本身,打開其屬性對話框,將其Caption屬性修改爲 “測試 ”, 以下統稱這個對話框爲測試對話框。
在MFC中,對資源的操作通常都是通過一個與資源相關的類來完成的。 對話框資源也有一個相應的基類:CDialogEx。由於CDialogEx類派生於CWnd類,所以它是一個與窗口相關的類,主要用來在屏幕上顯示一個對話框。由此可知,實際上,對話框本身也是 一個窗口界面。
既然在MFC中,對資源的操作是通過一個類來完成的,那麼就需要創建一個類與這個新建的對話框資源相關聯。爲此,我們雙擊剛剛新建的測試對話框將出現如圖所示的對話框,利用這個對話框就可以爲新建的測試對話框資源創建—個關聯的類。
在這裏插入圖片描述
我們將類名命名爲CTestDlg,其.h、.cpp文件名稱默認和類名一樣。如下圖所示:
在這裏插入圖片描述
點擊確定,這時,在 Dialog程序的類視圖選項卡中就可以看到這個新類。可以看到,這個CTestDlg 新類有兩個重要成員函數,其中一個就是它的構造函數,其定義代碼如下:

CTestDlg::CTestDlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_DIALOG1, pParent)
{

}   

可以看到,CTestDlg 類的構造函數首先調用其基類:CDialogEx的構造函數,並傳遞兩個參數:一個是CTestDlg 類的IDD成員,一個是父窗口指針。打開 CTestDlg 類的頭文件,就可以發現這個 IDD 就是這個對話框資源的ID, 代碼如下:

// 對話框數據
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_DIALOG1 };
#endif

CTestDlg 類的另一個函數是:DoDataExchange, 主要用來完成對話框數據的交換和校驗,其定義代碼如下所示:

void CTestDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

現在,我們就有了一個類 (CTestDlg) 與IDD_DIALOG1這個對話框資源相關聯了,就像程序中 CAboutDlg 類與lDD_ABOUTBOX 這個對話框資源相關聯一樣。接下來,我們希望在程序中顯示這個對話框窗口,爲此,可以爲Dialog程序主界面下的確定按鈕添加一個事件,當用戶單擊這個按鈕時就顯示這個測試對話框窗口。接下來如何顯示這個測試對話框就要看看我們自己的設置了,是模態對話框還是非模態呢?如何實現呢?我們一一進行展示。

模態對話框的創建

首先實現模態對話框的創建。創建模態對話框需要調用CDialog 類的成員函數:DoModal, (如下圖所示:)該函數的功能就是創建並顯示一個模態對話框,其返回值將作爲CDialog類的另一個成員函數:EndDialog 的參數,後者的功能就是關閉模態對話框。
圖1
在正式顯示模態對話框之前我們先爲主窗口的確定按鈕添加一個事件,來實現模態對話框的顯示。我們雙擊確定按鈕,在DialogDlg.cpp文件看到如下代碼:

void CDialogDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知處理程序代碼
	CDialogEx::OnOK();
}

我們在這個按鈕觸發事件下添加代碼,實現顯示模態對話框,代碼如下:

void CDialogDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知處理程序代碼
	CDialogEx::OnOK();
	CTestDlg dlg;
	dlg.DoModal();
}

上述代碼中,首先定義一個對話框對象:dlg, 然後利用這個對象調用DoModal函數以產生一個模態對話框。另外,在視類中並不知道這個CTestDlg 對話框是什麼樣的數據類型,所以還必須在視類的源文件中包含這個CTestDlg類的頭文件,結果如下圖所示,其中紅色箭頭所指的那行代碼就是需要添加的內容。
在這裏插入圖片描述
然後我們編譯運行,首先彈出主界面,如下圖所示:
在這裏插入圖片描述
然後點擊確定,結果如圖所示:
在這裏插入圖片描述
當前只能運行此模態對話框,且停止主窗口的運行,直到模態對話框退出,才允許主窗口運行。

非模態對話框的創建

如果要創建非模態對話框,則需要利用CDialog類的Create成員函數。該函數具有以下兩種形式的聲明:

virtual BOOL Create(LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL);
virtual BOOL Create(UINT nIDTemplate, CWnd* pParentWnd = NULL);

也就是說,Create函數的第一個參數可以是對話框資源的ID(nIDTemplate參數,或者也可以是對話框模板的名稱(lps zTemplateName參數)。這個函數的第二個參數指定了對話框的父窗口,如果其值是NULL,對話框的父窗口就是主應用程序窗口。對本例來說,如果這個父窗口參數值是NULL, 對話框的父窗口就是Dialog窗口。這裏,我們仍在主窗口的確定按鈕事件下實現創建非模態對話框的功能,則首先需要將上面創建模態對話框的代碼註釋起來,然後在其後面添加創建非模態對話框的代碼,結果如下:

void CDialogDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知處理程序代碼
	CDialogEx::OnOK();
	/*CTestDlg dlg;
	dlg.DoModal();*/
	CTestDlg dlg;
	dlg.Create(IDD_DIALOG1,this);
}

編譯運行程序, 單擊確定,發現並未出現測試對話框窗口。這裏,我們一定要注意,當利用Create函數創建非模態對話框時,還需要調用ShowWindow函數將這個對話框顯示出來。那爲什麼上面利用DoModal函數創建對話框時不需要呢?這是因爲DoModal函數本身就有顯示模態對話框的作用,所以對模態對話框來說,不需要再調用 ShowWindow函數來顯示對話框了,但非模態對話框需要調用此函數。因此,我們在上述所示代碼的最後再加上下而這行代碼:

dlg.ShowWindow(SW_SHOW);

編譯並運行程序,單擊確定,發現仍沒有出現測試對話框。噫~~問題出在哪裏了呢?我們回頭看看上面的代碼,發現這裏創建的非模態對話框對象 dlg 是一個局部對象,當程序執行時,會依次執行各條代碼,當OnBnClickedOk函數執行結束時,dlg這個對象的生命週期也就結束了,它就會銷燬與之相關聯的對話框資源。那爲什麼上面創建模態對話框時就可以使用局部對象呢?上面也已經說過了,在創建模態對話框時,當執行到調用 DoModal函數以顯示這個對話框時,程序就會暫停執行,直到模態對話框關閉之後,程序才繼續向下執行。也就是說,當模態對話框顯示時,程序中創建的dlg這個對象的生命週期並未結束。因此,在創建非模態對話框時,不能把對話框對象定義爲局部對象。查閱資料發現對於這個問題,有兩種解決辦法:一是把這個對話框對象定義爲視類的成員變量; 另一種方式是將它定義爲指針,在堆上分配內存。 因爲,在堆上分配的內存,與程序的整個生命週期是一致的,當然這裏是指程序中不主動銷燬的情況。那我們就試一下吧(不理解的就去查閱一下資料,我也是查了大量資料才知道的)
這裏,我們採用後一種方式,修改已有代碼,結果如下代碼所示:


void CDialogDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知處理程序代碼
	//CDialogEx::OnOK();
	/*CTestDlg dlg;
	dlg.DoModal();*/

	/*CTestDlg dlg;
	dlg.Create(IDD_DIALOG1,this);
	dlg.ShowWindow(SW_SHOW);*/
	CTestDlg *pDlg = new CTestDlg;
	pDlg->Create(IDD_DIALOG1, this);
	pDlg->ShowWindow(SW_SHOW);
}

編譯運行,見證奇蹟的時刻,如圖所示:
在這裏插入圖片描述
然而這中方法又引入了新的問題:我們必須釋放pDlg佔用的資源,否則會造成內存泄漏! 況且這裏pDlg還是一個局部指針變量,當它的生命週期結束時,在程序中就無法再引用它所指向的那塊內存了。
解決方法同樣有兩個:一是在主對話框類的定義中添加私有成員變量,然後在主對話框類的析構函數中調用delete函數釋放它指向的內存;二是在主對話框類中重載PostNcDestroy虛函數,釋放this指針指向的內存。
我們先用第一種方法具體操作如下:

  1. 先在DialogDlg.h文件中加入下面一行代碼
    在這裏插入圖片描述
  2. 添加私有成員變量和析構函數
    在這裏插入圖片描述
    在這裏插入圖片描述
  3. 在按鈕事件下編寫代碼:
void CDialogDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知處理程序代碼
	//CDialogEx::OnOK();
	/*CTestDlg dlg;
	dlg.DoModal();*/

	/*CTestDlg dlg;
	dlg.Create(IDD_DIALOG1,this);
	dlg.ShowWindow(SW_SHOW);*/
	//CTestDlg *pDlg = new CTestDlg;
	if (NULL == pDlg) {
		pDlg = new CTestDlg();
		pDlg->Create(IDD_DIALOG1);
	}
	pDlg->ShowWindow(SW_SHOW);
}

CDialogDlg::~CDialogDlg() {
	if (NULL != pDlg) {
		delete pDlg;
	}
}

  • 運行結果:
    在這裏插入圖片描述

我們再用第二種方法釋放內存,具體操作如下:

  • 先將第一種方法的代碼註釋掉,如下圖所示:
    在這裏插入圖片描述
    在這裏插入圖片描述

  • 爲主對話框重載PostNcDestroy虛函數
    在這裏插入圖片描述

  • 編寫代碼:

void CDialogDlg::OnBnClickedOk()
{
	// TODO: 在此添加控件通知處理程序代碼
	//CDialogEx::OnOK();
	/*CTestDlg dlg;
	dlg.DoModal();*/

	/*CTestDlg dlg;
	dlg.Create(IDD_DIALOG1,this);
	dlg.ShowWindow(SW_SHOW);*/
	//CTestDlg *pDlg = new CTestDlg;
	if (NULL == pDlg) {
		pDlg = new CTestDlg();
		pDlg->Create(IDD_DIALOG1);
	}
	pDlg->ShowWindow(SW_SHOW);
}
void CDialogDlg::PostNcDestroy()
{
	// TODO: 在此添加專用代碼和/或調用基類

	CDialogEx::PostNcDestroy();
	delete pDlg;
}

  • 運行結果:
    在這裏插入圖片描述

至此,我們的模態對話框和非模態對話框就創建完成了!!

如果這篇文章對你的學習起到一定的幫助,記得點個贊哦!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章