MFC模式窗口 CWnd的派生類-3、CDialog類

 

int CDialog::DoModal()
{
    // can be constructed with a resource template or InitModalIndirect
    ASSERT(m_lpszTemplateName != NULL || m_hDialogTemplate != NULL ||
        m_lpDialogTemplate != NULL);

    // load resource as necessary
    LPCDLGTEMPLATE lpDialogTemplate = m_lpDialogTemplate;
    HGLOBAL hDialogTemplate = m_hDialogTemplate;
    HINSTANCE hInst = AfxGetResourceHandle();
    if (m_lpszTemplateName != NULL)
    {
        hInst = AfxFindResourceHandle(m_lpszTemplateName, RT_DIALOG);
        HRSRC hResource = ::FindResource(hInst, m_lpszTemplateName, RT_DIALOG);
        hDialogTemplate = LoadResource(hInst, hResource);
    }
    if (hDialogTemplate != NULL)
        lpDialogTemplate = (LPCDLGTEMPLATE)LockResource(hDialogTemplate);

    // return -1 in case of failure to load the dialog template resource
    if (lpDialogTemplate == NULL)
        return -1;

    // disable parent (before creating dialog)
    HWND hWndParent = PreModal();
    AfxUnhookWindowCreate();
    BOOL bEnableParent = FALSE;
    if (hWndParent != NULL && ::IsWindowEnabled(hWndParent))
    {
        ::EnableWindow(hWndParent, FALSE);
        bEnableParent = TRUE;
    }

    TRY
    {
        // create modeless dialog
        AfxHookWindowCreate(this);
        if (CreateDlgIndirect(lpDialogTemplate,
                        CWnd::FromHandle(hWndParent), hInst))
        {
            if (m_nFlags & WF_CONTINUEMODAL)
            {
                // enter modal loop
                DWORD dwFlags = MLF_SHOWONIDLE;
                if (GetStyle() & DS_NOIDLEMSG)
                    dwFlags |= MLF_NOIDLEMSG;
                VERIFY(RunModalLoop(dwFlags) == m_nModalResult);
            }

            // hide the window before enabling the parent, etc.
            if (m_hWnd != NULL)
                SetWindowPos(NULL, 0, 0, 0, 0, SWP_HIDEWINDOW|
                    SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER);
        }
    }
    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();
    PostModal();

    // unlock/free resources as necessary
    if (m_lpszTemplateName != NULL || m_hDialogTemplate != NULL)
        UnlockResource(hDialogTemplate);
    if (m_lpszTemplateName != NULL)
        FreeResource(hDialogTemplate);

    return m_nModalResult;
}
int CWnd::RunModalLoop(DWORD dwFlags)
{
    ASSERT(::IsWindow(m_hWnd)); // window must be created
    ASSERT(!(m_nFlags & WF_MODALLOOP)); // window must not already be in modal state

    // for tracking the idle time state
    BOOL bIdle = TRUE;
    LONG lIdleCount = 0;
    BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE) && !(GetStyle() & WS_VISIBLE);
    HWND hWndParent = ::GetParent(m_hWnd);
    m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);
    MSG* pMsg = &AfxGetThread()->m_msgCur;

    // acquire and dispatch messages until the modal state is done
    for (;;)
    {
        ASSERT(ContinueModal());

        // phase1: check to see if we can do idle work
        while (bIdle &&
            !::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))
        {
            ASSERT(ContinueModal());

            // show the dialog when the message queue goes idle
            if (bShowIdle)
            {
                ShowWindow(SW_SHOWNORMAL);
                UpdateWindow();
                bShowIdle = FALSE;
            }

            // call OnIdle while in bIdle state
            if (!(dwFlags & MLF_NOIDLEMSG) && hWndParent != NULL && lIdleCount == 0)
            {
                // send WM_ENTERIDLE to the parent
                ::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_hWnd);
            }
            if ((dwFlags & MLF_NOKICKIDLE) ||
                !SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))
            {
                // stop idle processing next time
                bIdle = FALSE;
            }
        }

        // phase2: pump messages while available
        do
        {
            ASSERT(ContinueModal());

            // pump message, but quit on WM_QUIT
            if (!AfxGetThread()->PumpMessage())
            {
                AfxPostQuitMessage(0);
                return -1;
            }

            // show the window when certain special messages rec'd
            if (bShowIdle &&
                (pMsg->message == 0x118 || pMsg->message == WM_SYSKEYDOWN))
            {
                ShowWindow(SW_SHOWNORMAL);
                UpdateWindow();
                bShowIdle = FALSE;
            }

            if (!ContinueModal())
                goto ExitModal;

            // reset "no idle" state after pumping "normal" message
            if (AfxGetThread()->IsIdleMessage(pMsg))
            {
                bIdle = TRUE;
                lIdleCount = 0;
            }

        } while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));
    }

ExitModal:
    m_nFlags &= ~(WF_MODALLOOP|WF_CONTINUEMODAL);
    return m_nModalResult;
}

 


 

CWnd的派生類-3、CDialog類

對話框與普通窗口的區別僅在於,對話框是通過對話框模板建立起來的。只需要一個以模板爲實參的創建命令,如CDialog::Create(),就可以完成對話框窗口及其子控件的創建工作,所有創建細節都由對話框模板來指示。而對於普通窗口,窗口及其包含的子控件必須逐一創建,而且要指定窗口風格等詳細參數。對話框是最基本的可視化編程方法,一個應用程序往往包含衆多的對話框資源模板和封裝類,而普通窗體(包括框架窗體)卻寥寥無幾。但對話框的使用,只是方便了窗體和控件的創建過程,其本質與普通窗體無任何區別。

下面並不準備陳述對話框的技術細節,只與讀者討論兩個相關問題:一是模態對話框的消息循環,二是對話框的命令消息路由。

7.4  模態對話框的消息循環
模態對話框是程序中最常用的窗口,當調用對話框的DoModal()成員後,就創建了一個模態對話框。其特點是,除了這個對話框窗體外,幾乎不能操作程序的其他部分。但如果此時已經打開了兩個以上的主窗體,只能禁止模態對話框所在的主窗口及其子窗口,包括主窗口下屬的彈出對話框,但不包括下屬的重疊窗口和普通彈出窗口。即當模態對話框彈出時,禁止了它的父窗口及大部分兄弟窗口的操作;模態對話框關閉後,被禁用的窗口將恢復使用。

7.4.1  模態對話框的創建與模式循環
其實,“模態”並不是對話框的專利,模態特性是封裝在CWnd中的。所以,如果採取與模態對話框相同的創建方法,普通窗體也可以是模態的。這個方法就是在創建窗體後,調用CWnd::RunModalLoop()模式循環函數。該函數與前面講過的CWinThread::Run()非常相似,也是一個消息循環泵,而且CWnd:: RunModalLoop()的消息處理還要稍複雜一些。在學習這個模式循環函數之前,首先來了解模態對話框的創建與銷燬過程。下面是對CDialog::DoModal()函數的簡單縮寫。

int CDialog::DoModal()

{  //裝入對話框模板資源

HINSTANCE hInst = AfxGetResourceHandle();   

hDialogTemplate = LoadResource(hInst, hResource);

         if (lpDialogTemplate == NULL)

                  return -1;

         //在建立模態對話框之前,禁止父窗口的鼠標和鍵盤輸入

         HWND hWndParent = PreModal();//取得父窗口句柄(一般是程序主窗口,如主框架)

         BOOL bEnableParent = FALSE;

         if (hWndParent != NULL && ::IsWindowEnabled(hWndParent))

         {

//禁止父窗口也將間接地禁止父窗口的下屬窗口,但不包括下屬的重疊窗口和普通彈出窗口

                  ::EnableWindow(hWndParent, FALSE);

                  bEnableParent = TRUE;

         }

         //通過資源模板創建對話框及其子控件

         if (CreateDlgIndirect(lpDialogTemplate,       CWnd::FromHandle(hWndParent), hInst))

                  { //創建成功

                                   //進入模式循環

                                   DWORD dwFlags = MLF_SHOWONIDLE;

                                   VERIFY(RunModalLoop(dwFlags) == m_nModalResult);

//當用戶選擇IDOK或IDCANCEL時,模式循環退出,對話框將被銷燬

                  }       

         if (bEnableParent)

                  ::EnableWindow(hWndParent, TRUE);//恢復父窗口的工作狀態,間接地恢復其兄弟窗口

         if (hWndParent != NULL && ::GetActiveWindow() == m_hWnd)

                  ::SetActiveWindow(hWndParent);//激活父窗口

         //銷燬該模式對話框

         DestroyWindow();

         return m_nModalResult;

}

從以上代碼可知,在模態對話框創建之前,首先要將該程序的主窗口(也是該對話框未來的宿主窗口)禁止。這樣,該主窗口以及主窗口下屬的所有子窗口和彈出對話框都被禁止。然後調用CreateDlgIndirect()創建對話框。注意,因爲該對話框是在禁止主窗口之後創建的,所以它是活動的;也就是說,當前主窗口及其下屬的所有窗口中,除重疊窗口和普通彈出窗口外,只有它是活動的。這是模態對話框的特點。可見,只要在該對話框銷燬時重新激活主窗口就可以了,至此,已經完成了模態對話框的創建工作。但閱讀以上代碼會發現,事情並不這麼簡單,在創建對話框後還需進入模式循環,對話框關閉後,模式循環才退出。模式循環究竟有什麼作用呢?

其實,由RunModalLoop()實現的模態循環,並不是創建模態窗口或模態對話框的方式。如上所述,只要在對話框創建之前禁止主窗口,在對話框銷燬時激活主窗口,在形式上就已經實現了所謂的模態對話框。模式循環是專爲模態窗口設計的一個消息循環,這個消息循環完成UI線程消息循環(由CWinThread::Run()封裝)的全部功能,同時爲處理模態窗口的特殊消息,增加了必要的處理代碼。當模態窗口創建後,就進入這個消息循環,其中的消息循環泵暫時代替了UI線程的消息循環泵,爲所有的窗口提取並分發消息。但所有被禁止的窗口無法接收鼠標和鍵盤消息,除非使用PostMessage()命令。

下面講解CWnd::RunModalLoop()是如何工作的。

/*******************形參dwFlags可以是下列值的組合*****************

MLF_NOIDLEMSG     當消息隊列空閒時,不發送WM_ENTERIDLE消息給主窗口

MLF_NOKICKIDLE    當消息隊列空閒時,不發送WM_KICKIDLE消息給當前模態窗口

MLF_SHOWONIDLE當消息隊列空閒時,刷新顯示當前對話框(僅一次)*/

int CWnd::RunModalLoop(DWORD dwFlags)

{

         ASSERT(::IsWindow(m_hWnd)); // window must be created

//m_nFlags標誌當前對話框的狀態,值WF_MODALLOOP標誌已經進入模態

         ASSERT(!(m_nFlags & WF_MODALLOOP));

         //標誌空閒處理入口的狀態

         BOOL bIdle = TRUE;

         //連續處理WM_KICKIDLE消息的次數

         LONG lIdleCount = 0;

//空閒時是否刷新顯示當前對話框(僅一次)

         BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE) && !(GetStyle() & WS_VISIBLE);

         HWND hWndParent = ::GetParent(m_hWnd);

         //設置對話框狀態標誌

         m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);

         MSG* pMsg = &AfxGetThread()->m_msgCur;//取得存儲當前消息的緩衝

         for (;;)

         {

                  ASSERT(ContinueModal());//檢查是否錯誤地結束了模式循環

                  //循環1:用於調度空閒處理

                  while (bIdle &&

                          !::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))

                  {

                          ASSERT(ContinueModal());

                          if (bShowIdle)

                          {        //顯示刷新當前窗口

                                   ShowWindow(SW_SHOWNORMAL);

                                   UpdateWindow();

                                   bShowIdle = FALSE;

                          }

                          if (!(dwFlags & MLF_NOIDLEMSG) && hWndParent != NULL && lIdleCount == 0)

                          {

                                   //給父窗口發送WM_ENTERIDLE 消息

         ::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_ hWnd);

                          }

                          //或關係,如果第一個條件不成立,執行第二個條件

                          if ((dwFlags & MLF_NOKICKIDLE) ||

                                   !SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))

                          {        //可見,在模態對話框內,可以將WM_KICKIDLE消息作爲空閒消息進行處理

                                   bIdle = FALSE;

                          }

                  }

                  //循環2:提取並分發消息 

                  do

                  {        ASSERT(ContinueModal());

                          // pump message, but quit on WM_QUIT

                          if (!AfxGetThread()->PumpMessage())

                          {        AfxPostQuitMessage(0);

                                   return-1;

                          }

                          //收到特殊消息,是否刷新顯示該對話框

                          if (bShowIdle &&

                                   (pMsg->message == 0x118 || pMsg->message == WM_SYSKEYDOWN))

                          {        ShowWindow(SW_SHOWNORMAL);

                                   UpdateWindow();

                                   bShowIdle = FALSE;

                          }

                          if (!ContinueModal())  //可能是關閉當前對話框的消息,判斷是否該結束模式循環

                                   goto ExitModal;

                          //根據剛剛處理的消息類型,判斷是否應該在沒有消息到來時立即進行空閒處理

                          if (AfxGetThread()->IsIdleMessage(pMsg))

                          {

         bIdle = TRUE;

                                   lIdleCount = 0;

                          }

                  } while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));

         }

ExitModal: //用戶已關閉對話框,結束模式循環

         m_nFlags &= ~(WF_MODALLOOP|WF_CONTINUEMODAL); //清空對話框的模態標誌

         return m_nModalResult;     //返回對話框的關閉代碼(如IDOK、IDCANCEL)

}

通過比較CWinThread::Run()與CWnd::RunModalLoop()兩個消息循環的差異,不難發現後者爲模態對話框做了哪些工作。模式循環既可以向父窗口發送WM_ENTERIDLE消息,也可以向當前窗口發送與空閒消息等同的WM_KICKIDLE消息,使得模態對話框有能力在空閒時完成一定的操作。同時允許刷新顯示對話框。但注意,CWinThread::OnIdle()在模式循環中不被調用。

在對CWinThread::PumpMessage()的闡述中,曾經提及WM_KICKIDLE消息,它在消息泵中不被分發處理。所以,在模式循環中使用SendMessage()而不是PostMessage()發送該消息。WM_KICKIDLE消息像一個未公開的祕密,沒有正式的文檔說明,它在afxpriv.h頭文件中定義。如果你的模態對話框需要空閒處理,應包含這個頭文件,然後手工添加消息映射即可。

7.4.2  結束模式循環
閱讀RunModalLoop()代碼可知,當調用ContinueModal()返回FALSE時,模式循環結束。該函數只是檢查m_nFlags狀態標誌。

BOOL CWnd::ContinueModal()

{

         return m_nFlags & WF_CONTINUEMODAL;

}

顯然,當用戶單擊IDOK或IDCANCEL時,改變了成員m_nFlags的狀態,使得循環結束。下面列出相關的幾個成員函數:

void CDialog::OnOK()

{        if (!UpdateData(TRUE))

         {        return;

         } //以IDOK爲結束代碼

         EndDialog(IDOK);

}

void CDialog::OnCancel()

{        //以IDCANCEL爲結束代碼

         EndDialog(IDCANCEL);}

void CDialog::EndDialog(int nResult)

{

         ASSERT(::IsWindow(m_hWnd));

         if (m_nFlags & (WF_MODALLOOP|WF_CONTINUEMODAL))

//如果當前對話框是模態的,則結束模式循環

         EndModalLoop(nResult);

         ::EndDialog(m_hWnd, nResult);//調用API結束有關本對話框的系統處理

}

void CWnd::EndModalLoop(int nResult)

{       

ASSERT(::IsWindow(m_hWnd));

         //設置返回代碼

         m_nModalResult = nResult;

         if (m_nFlags & WF_CONTINUEMODAL)

         {        //設置模式循環結束標誌,發送空消息通知消息泵

                  m_nFlags &= ~WF_CONTINUEMODAL;

                  PostMessage(WM_NULL);

         } }

可見,只要在對話框中調用CDialog::EndDialog()就可以結束模式循環。但結束模式循環後,還必須調用DestroyWindow()銷燬對話框,這個工作在DoModal()退出前已經完成。但如果使用CDialog::Create()創建了一個非模態對話框,就不得不在直接或間接調用EndDialog()關閉對話框後,親自調用DestroyWindow()了。

7.4.3  創建普通的模態窗口
通過以上對模態對話框的學習,已經掌握了創建模態窗口的技術。如果需要一個普通的模態窗口,可以參考以下步驟進行操作。

(1)調用EnableWindow()禁止程序主窗口。如果當前存在多個主窗口,禁止與該模態窗口有所屬關係的主窗口。

(2)使用CWnd::Create()等創建命令,創建該窗口。可以是彈出窗口,也可以是重疊窗口。

(3)調用模式循環函數RunModalLoop(DWORD dwFlags),根據實際需要設置實參。如果需要空閒處理,還須手工添加消息映射。

(4)當關閉窗口時調用EndModalLoop(int nResult),根據實際需要設置結束代碼。

(5)激活主窗口,調用DestroyWindow()摧毀當前模態窗口。一定要確保在窗口銷燬前已經結束了模式循環。

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