vc的模態對話框和非模態對話框
作者:佚名 時間:2012-11-18 分享到:
非模態的對話框是獨立的,就是說和主程序可以同時交換數據,而模態的對話框則只能和自己交換。換句話說,模態對話框很霸道,你開了它就無法在同一個應用程序中的其他地方干活了,比如那個經常用的openfile dialog。非模態對話框就很隨和,開了它你還可以愛干嘛干嘛。還有一個區別在于內存分配,new在堆中,另一在棧中,一般說兩者一樣,但如果你在對話框中開了很大的空間做成員,第二種就會有問題了,因為?臻g是受限的。但據說解決棧溢出用static申明,在全局數據區分配內存就可以了(我沒試過)! 模態消息不進message loop;非模態消息進入message loop。
如何使用vc的非模態對話框
1、非模態對話框的模板必須具有Visible風格,否則對話框將不可見,而模態對話框則無需設置該項風格。更保險的辦法是調用CWnd::ShowWindow(SW_SHOW)來顯示對話框,而不管對話框是否具有Visible風格。
2、非模態對話框對象是用new操作符在堆中動態創建的,而不是以成員變量的形式嵌入到別的對象中或以局部變量的形式構建在堆棧上。通常應在對話框的擁有者窗口類內聲明一個指向對話框類的指針成員變量,通過該指針可訪問對話框對象。
3、* 通過調用CDialog::Create函數來啟動對話框,而不是CDialog::DoModal,這是模態對話框的關鍵所在。由于Create函數不會啟動新的消息循環,對話框與應用程序共用同一個消息循環,這樣對話框就不會壟斷用戶的輸入。Create在顯示了對話框后就立即返回,而DoModal 是在對話框被關閉后才返回的。眾所周知,在MFC程序中,窗口對象的生存期應長于對應的窗口,也就是說,不能在未關閉屏幕上窗口的情況下先把對應的窗口對象刪除掉。由于在Create返回后,不能確定對話框是否已關閉,這樣也就無法確定對話框對象的生存期,因此只好在堆中構建對話框對象,而不能以局部變量的形式來構建之。
4、代碼示例:
對話框資源ID為:IDD_MYDIALOG
對話框的類定義為:CMyDialog
CMyView的成員變量為:CMyDialog* m_myDlg
CMyView::OnOpenDialogButton()
{
CMainFrame* pWnd=(CMainFrame*)AfxGetMainWnd();
ASSERT_VALID(pWnd); //定義父窗口指針pWnd
m_myDlg=new CMyDialog(pWnd); //堆分配非模態對話框內存空間, //CMyDialog(CWnd* pParent=NULL)為構造函數
m_myDlg->Create(IDD_MYDIALOG,pWnd);
m_myDlg->ShowWindow(SW_SHOW);
//可用this指針代替pWnd指針,則省略頭兩行
}
5、必須有一個標志表明非模態對話框是否是打開的。這樣做的原因是用戶有可能在打開一個模態對話框的情況下,又一次選擇打開命令。程序根據標志來決定是打開一個新的對話框,還是僅僅把原來打開的對話框激活。通常可以用擁有者窗口中的指向對話框對象的指針作為這種標志,當對話框關閉時,給該指針賦NULL值,以表明對話框對象已不存在了。
[注]:在C++編程中,判斷一個位于堆中的對象是否存在的常用方法是判斷指向該對象的指針是否為空。這種機制要求程序員將指向該對象的指針初始化為NULL值,在創建對象時將返回的地址賦給該指針,而在刪除對象時將該指針置成NULL值。
6、必須調用CWnd::DestroyWindow而不是CDialog::EndDialog來關閉非模態對話框。調用 CWnd::DestroyWindow是直接刪除窗口的一般方法。由于缺省的CDialog::OnOK和CDialog::OnCancel函數均調用EndDialog,故程序員必須編寫自己的OnOK和OnCancel函數并且在函數中調用DestroyWindow來關閉對話框。
7、因為是用new操作符構建非模態對話框對象,因此必須在對話框關閉后,用delete操作符刪除對話框對象。在屏幕上一個窗口被刪除后,框架會調用CWnd::PostNcDestroy,這是一個虛擬函數,程序可以在該函數中完成刪除窗口對象的工作,具體代碼如下
void CModelessDialog::PostNcDestroy
{
delete this; //刪除對象本身
}
這樣,在刪除屏幕上的對話框后,對話框對象將被自動刪除。擁有者對象就不必顯式的調用delete來刪除對話框對象了。也可以通過由的DestroyWindow()引起的WM_DESTROY消息處理函數OnDestroy()種實現delete this操作。
void CModelessDlg::OnDestroy()
{
CDialog::OnDestroy();
delete this;
}
模態對話框的使用
在下面的一篇文章里敘述的很詳盡,所以就摘抄過來,呵呵
Visual C++模態對話框消息處理機制剖析
摘要:消息驅動機制是Windows操作系統的根本,模態對話框消息處理又是不同于一般消息處理的特殊形式。通過分析這種消息機制的原理,可用來處理類似的程序設計要求。
在Windows操作系統中,面向用戶的GUI基本上可分為對話框形式和文檔/視圖兩種表現形式。對話框的顯示方式又可分為模態對話框和非模態對話框,以適應不同的用戶交互需求。由于對話框和文檔/視圖框架結構各有特色,能不能將文檔/視圖框架結構當作一對話框來使用,或在對話框中實現文檔/視圖框架結構內的特色功能呢,答案是肯定的。
下面,從Windows 操作系統消息驅動機制開始,進而探討模態對話框實現過程的消息封裝、傳遞和處理機制,最后以模態的形式顯示應用到文檔/視圖框架結構中的實例作為對所講內容的驗證和實踐。
一、Windows消息機制
Windows是一種面向對象的體系結構,Windows環境和應用程序都是通過消息來交互的。Windows應用程序開始執行后,Windows為該程序創建一個"消息隊列(message queue)",用以存放郵寄給該程序可能創建的各種不同窗口的消息。消息隊列中消息的結構(MSG)為:
typedef struct tagMSG{
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
}MSG;
其中第一個成員變量是用來標識接收消息的窗口句柄;第二個參數便是消息標識號,如WM_PAINT;第三個和第四個參數的具體意義同message值有關,均為消息參數。前四個參數是非常重要和經常用到的,至于后兩個參數則分別表示郵寄消息的時間和光標位置(屏幕坐標)。把消息傳送到應用程序有兩種方法:一種是由系統將消息"郵寄(post)"到應用程序的"消息隊列"這是"進隊消息"Win32 API有對應的函數: PostMessage(),此函數不等待該消息處理完就返回;而另一種則是由系統在直接調用窗口函數時將消息"發送(send)"給應用程序的窗口函數,屬于"不進隊消息"對應的函數是SendMessage()其必須等待該消息處理完后方可返回。
對于每一個正在執行的Windows應用程序,系統為其建立一個"消息隊列",即應用程序隊列,用來存放該程序可能創建的各種窗口的消息。應用程序中含有一段稱作"消息循環"的代碼,用來從消息隊列中檢索這些消息并把它們分發到相應的窗口函數中。
消息循環代碼是應用程序中主函數winmain ( )中類似如下的程序段:
while(GetMessage(&&msg,NULL,NULL,NULL))
{ file://從消息隊列中取得消息
TranslateMessage(&&msg);
file://檢索并生成字符消息WM_CHAR
DispatchMessage(&&msg);
file://將消息發送給相應的窗口函數
}
由此可見,所謂"消息循環",實際是程序循環。
Windows 應用程序創建的每個窗口都在系統核心注冊一個相應的窗口函數,窗口函數程序代碼形式上是一個巨大的switch 語句,用以處理由消息循環發送到該窗口的消息,窗口函數由Windows 采用消息驅動的形式直接調用,而不是由應用程序顯示調用的,窗口函數處理完消息后又將控制權返回給Windows。
二、模態對話框的消息處理
由上面我們看到,Windows是一個巨大的消息驅動結構,由用戶發出消息,系統響應處理。非模態對話框是響應一個消息,系統處理一個消息,處理完畢后返回控制權給Windows。文檔/視圖框架結構與其類似。模態對話框在對話框創建后,掛起外部的消息,只是響應對話框內部的消息,而外部消息則全部"過濾"掉了,直到系統接收到WM_DESTROY或WM_CLOSE后,系統返回控制權給模態對話框創建前的線程,繼續模態對話框創建前的線程將執行下面的代碼。
讓我們看看下面的對話框DoModal實現代碼:
{
… …
// Disable 父窗口 (在創建對話框前)
HWND hWndParent = PreModal();
AfxUnhookWindowCreate();
BOOL bEnableParent = FALSE;
if (hWndParent != NULL && ::IsWindowEnabled(hWndParent))
{
::EnableWindow(hWndParent, FALSE);
bEnableParent = TRUE;
}
TRY
{
// 創建模態對話框
AfxHookWindowCreate(this);
if (CreateDlgIndirect(lpDialogTemplate,CWnd::FromHandle(hWndParent), hInst))
{
if (m_nFlags & WF_CONTINUEMODAL)
{
// 進入模式循環
DWORD dwFlags = MLF_SHOWONIDLE;
if (GetStyle() & DS_NOIDLEMSG) dwFlags |= MLF_NOIDLEMSG;
VERIFY(RunModalLoop(dwFlags) == m_nModalResult);
}
}
}
CATCH_ALL(e)
{
DELETE_EXCEPTION(e);
m_nModalResult = -1;
}
END_CATCH_ALL
file://Enable 父窗口
if (bEnableParent)
::EnableWindow(hWndParent, TRUE);
if (hWndParent != NULL && ::GetActiveWindow() == m_hWnd)
::SetActiveWindow(hWndParent);
// 刪除對話框
DestroyWindow();
PostModal();
… …
}
可以看到,在此實現代碼中,并沒有開辟新的線程。系統是在RunModalLoop()中進行消息循環。當 m_nFlags 為 WF_CONTINUEMODAL時,系統繼續模式狀態。RunModalLoop()函數實際上也是一for(;;)循環,控制重新分派Windows 消息。直到ContinueModal()返回FALSE,而當調用EndModalLoop()時,ContinueModal()返回FALSE。此時,標志著模態顯示的結束。因此,實現模態對話框消息處理的核心部分為RunModalLoop()和EndModalLoop()函數。
三、以模態的形式顯示應用到文檔/視圖框架結構實例
(1)新建一工程文件:ModeFrame,選取MFC AppWizard(exe)。
。2)第二步選取Single document(單文檔)。
。3)其余幾步均為缺省值。
。4)用ClassWizard添加一新類CSubModeFrame,以CFrameWnd為基類。
。5)添加CsubModeFrame的實現函數DoMode();
int CsubModeFrame::DoModal()
{
HWND hWndParent = m_hWndPrt;
CRect rc(0,0,400,400);
CWnd *pParent = CWnd::FromHandle(hWndParent);
DWORD dwStyle=WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_POPUP | WS_THICKFRAME | WS_VISIBLE | WS_SYSMENU | WS_CAPTION;
if(!Create(NULL,"模態文檔/試圖框架 ",dwStyle,rc,pParent,NULL)) return FALSE;
BOOL bEnableParent = FALSE;
if (hWndParent != NULL && ::IsWindowEnabled(hWndParent))
{
::EnableWindow(hWndParent,FALSE);
::EnableWindow(m_hWnd,TRUE);
bEnableParent = TRUE;
}
CenterWindow();
TRY
{
// enter modal loop
DWORD dwFlags = MLF_SHOWONIDLE;
if (GetStyle() & DS_NOIDLEMSG) dwFlags |= MLF_NOIDLEMSG;
VERIFY(RunModalLoop(dwFlags) == m_nModalResult);
}
CATCH_ALL(e)
{
DELETE_EXCEPTION(e);
m_nModalResult = -1;
}
END_CATCH_ALL
if (bEnableParent)
::EnableWindow(hWndParent, TRUE);
if (hWndParent != NULL && ::GetActiveWindow() == m_hWnd)
::SetActiveWindow(hWndParent);
// destroy modal window
DestroyWindow();
return m_nModalResult;
}
(6)添加CsubModeFrame的實現函數EndMode()
void CSubFrame::EndModal(){
ASSERT(::IsWindow(m_hWnd));
if (m_nFlags & (WF_MODALLOOP|WF_CONTINUEMODAL)) {
EndModalLoop(1);
}
}
(7)添加CModeFrameView的實現函數OnLButtonDblClk()
在此函數的消息處理中:可以象顯示對話框一樣處理CsubModeFrame類。
CSubModeFrame SubModeFrame;
If(SubModeFrame.DoMode()){ MessageBox("Mode Ok");}