MFC之对话框(二)

对话框经常被使用,因为对话框可以从模板创建,而对话框模板是可以使用资源编辑器方便地进行编辑的。

一,模式与非模式对话框
1.模式对话框
    一个模式对话框是一个有系统菜单、标题栏、边线等的弹出式窗口。在创建对话框时指定WS_POPUP, WS_SYSMENU, WS_CAPTION和 DS_MODALFRAME风格。即使没有指定WS_VISIBLE风格,模

式对话框也会被显示。创建对话框窗口时,将发送WM_INITDIALOG消息(如果指定对话框的DS_SETFONT风格,还有WM_SETFONT消息)给对话框过程。
    对话框窗口被创建之后,Windows使得它成为一个激活的窗口,它保持激活直到对话框过程调用::EndDialog函数结束对话框的运行或者Windows激活另一个应用程序为止,在激活时,用户

或者应用程序不可以激活它的所属窗口(Owner window)。从某个窗口创建一个模式对话框时,Windows自动地禁止使用(Disable)这个窗口和它的所有子窗口,直到该模式对话框被关闭和

销毁。虽然对话框过程可以Enable所属窗口,但是这样做就失去了模式对话框的作用,所以不鼓励这样做。
    Windows创建模式对话框时,给当前捕获鼠标输入的窗口(如果有的话)发送消息WM_CANCLEMODE。收到该消息后,应用程序应该终止鼠标捕获(Release the mouse capture)以便于用户能

把鼠标移到模式对话框;否则由于Owner窗口被禁止,程序将失去鼠标输入。
   为了处理模式对话框的消息,Windows开始对话框自身的消息循环,暂时控制整个应用程序的消息队列。如果Windows收到一个非对话框消息时,则它把消息派发给适当的窗口处理;如果收

到了WM_QUIT消息,则把该消息放回应用程序的消息队列里,这样应用程序的主消息循环最终能处理这个消息。
   当应用程序的消息队列为空时,Windows发送WM_ENTERIDLE消息给Owner窗口。在对话框运行时,程序可以使用这个消息进行后台处理,当然应该注意经常让出控制给模式对话框,以便它能

接收用户输入。如果不希望模式对话框发送 WM_ENTERIDlE消息,则在创建模式对话框时指定DS_NOIDLEMSG风格。
   一个应用程序通过调用::EndDialog函数来销毁一个模式对话框。一般情况下,当用户从系统菜单里选择了关闭(Close)命令或者按下了确认(OK)或取消(CANCLE)按钮,::EndDialog

被对话框过程所调用。调用::EndDialog时,指定其参数nResult的值,Windows将在销毁对话框窗口后返回这个值,一般,程序通过返回值判断对话框窗口是否完成了任务或者被用户取消。

 

2. 无模式对话框
   一个无模式对话框是一个有系统菜单、标题栏、边线等的弹出式窗口。在创建对话框模板时指定WS_POPUP、WS_CAPTION、WS_BORDER和WS_SYSMENU风格。如果没有指定WS_VISIBLE风格,无

模式对话框不会自动地显示出来。一个无模式对话框既不会禁止所属窗口,也不会给它发送消息(WM_ENTERIDlE)。当创建一个无模式对话框时,Windows使它成为活动窗口,但用户或者程序

可以随时改变和设置活动窗口。如果对话框失去激活,那么即使所属窗口是活动的,在Z轴顺序上,它仍然在所属窗口之上。
   应用程序负责获取和派发输入消息给对话框。大部分应用程序使用主消息循环来处理,但是为了用户可以使用键盘在控制窗口之间移动或者选择控制窗口,应用程序应该调

用::IsDialogMessage函数。
   这里,顺便解释::IsDialogMessage函数。虽然该函数是为无模式对话框设计的,但是任何包含了控制子窗口的窗口都可以调用它,用来实现类似于对话框的键盘选择操作。
当::IsDialogMessage处理一个消息时,它检查键盘消息并把它们转换成相应对话框的选择命令。例如,当Tab 键被压下时,下一个或下一组控制被选中,当Down Arrow键按下后,一组控制中

的下一个控制被选择。::IsDialogMessage完成了所有必要的消息转换和消息派发,所以该函数处理的消息一定不要传递给TranslateMessage和DispatchMessage处理。一个无模式对话框不能

像模式对话框那样返回一个值给应用程序。但是对话框过程可以使用::SendMessage给所属窗口传递信息。
   在应用程序结束之前,它必须销毁所有的无模式对话框。使用::DestroyWindow销毁一个无模式对话框,不是使用::EndDiaLog。一般来说,对话框过程响应用户输入,如用户选择了“取消

”按钮,则调用::DestroyWindow;如果用户没有有关动作,则应用程序必须调用::DestroyWindow。

 

二,对话框的MFC实现
1.在MFC中,对话框窗口的功能主要由CWnd和CDialog两个类实现。

[cpp]  view plain copy
  1. class CDialog : public CWnd  
  2. {  
  3.     DECLARE_DYNAMIC(CDialog)  
  4. //初始化函数  
  5. public:  
  6.     virtual BOOL Create(LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL);  
  7.     virtual BOOL Create(UINT nIDTemplate, CWnd* pParentWnd = NULL);  
  8.     virtual BOOL CreateIndirect(LPCDLGTEMPLATE lpDialogTemplate, CWnd* pParentWnd = NULL,void* lpDialogInit = NULL);  
  9.     virtual BOOL CreateIndirect(HGLOBAL hDialogTemplate, CWnd* pParentWnd = NULL);  
  10.         BOOL InitModalIndirect(LPCDLGTEMPLATE lpDialogTemplate, CWnd* pParentWnd = NULL,void* lpDialogInit = NULL);  
  11.     BOOL InitModalIndirect(HGLOBAL hDialogTemplate, CWnd* pParentWnd = NULL);  
  12. //构造函数  
  13. public:  
  14.         CDialog();  
  15.     explicit CDialog(LPCTSTR lpszTemplateName, CWnd* pParentWnd = NULL);  
  16.     explicit CDialog(UINT nIDTemplate, CWnd* pParentWnd = NULL);  
  17. // 操作函数  
  18. public:  
  19.     // modal processing  
  20.     virtual INT_PTR DoModal();  
  21.   
  22.     // support for passing on tab control - use 'PostMessage' if needed  
  23.     void NextDlgCtrl() const;  
  24.     void PrevDlgCtrl() const;  
  25.     void GotoDlgCtrl(CWnd* pWndCtrl);  
  26.   
  27.     // default button access  
  28.     void SetDefID(UINT nID);  
  29.     DWORD GetDefID() const;  
  30.   
  31.     // termination  
  32.     void EndDialog(int nResult);  
  33.   
  34. //重载函数  
  35. protected:  
  36.     virtual void OnOK();  
  37.     virtual void OnCancel();  
  38. public:  
  39.     virtual BOOL OnInitDialog();  
  40.     virtual void OnSetFont(CFont* pFont);  
  41. public:  
  42.     virtual ~CDialog();  
  43.     virtual BOOL PreTranslateMessage(MSG* pMsg);  
  44.     virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo);  
  45.     virtual BOOL CheckAutoCenter();  
  46.   
  47. protected:  
  48.     UINT m_nIDHelp;                 // Help ID (0 for none, see HID_BASE_RESOURCE)  
  49.     LPCTSTR m_lpszTemplateName;     // name or MAKEINTRESOURCE  
  50.     HGLOBAL m_hDialogTemplate;      // indirect (m_lpDialogTemplate == NULL)  
  51.     LPCDLGTEMPLATE m_lpDialogTemplate;  // indirect if (m_lpszTemplateName == NULL)  
  52.     void* m_lpDialogInit;           // DLGINIT resource data  
  53.     CWnd* m_pParentWnd;             // parent/owner window  
  54.     HWND m_hWndTop;                 // top level parent window (may be disabled)  
  55.   
  56.         virtual void PreInitDialog();  
  57.     HWND PreModal();  
  58.     void PostModal();  
  59. };  

2.模式对话框的实现
   在Win32 SDK编程下的模式对话框使用了Windows提供给对话框窗口的窗口过程和自己的对话框过程,对话框过程将被窗口过程调用。但在MFC下,所有的窗口类都使用了同一个窗口过程,

CDialog也不例外。CDialog对象在创建Windows对话框时,采用了类似于CWnd的创建函数过程,采用子类化的手段将Windows提供给对话框的窗口过程取代为AfxWndProc或者AfxBaseWndProc,

同时提供了对话框过程AfxDlgProc。那么,这些“过程”是如何实现或者协调的呢?下文将予以分析。

I.MFC对话框过程
//MFC对话框过程AfxDlgProc的原型和实现如下:

[cpp]  view plain copy
  1. BOOL CALLBACK AfxDlgProc(HWND hWnd,UINT message, PARAM, LPARAM)  
  2. {  
  3.     if (message == WM_INITDIALOG)  
  4.     {  
  5.          //处理WM_INITDIALOG消息  
  6.          CDialog* pDlg = DYNAMIC_DOWNCAST(CDialog,CWnd::FromHandlePermanent(hWnd));  
  7.          if (pDlg != NULL)  
  8.              return pDlg->OnInitDialog();  
  9.          else  
  10.              return 1;  
  11.     }  
  12.   
  13.     return 0;  
  14. }  

   由上可以看出,MFC的对话框函数AfxDlgProc仅处理消息WM_INITDIALOG,其他都留给对话框窗口过程处理。因此,它不同于SDK编程的对话框过程。程序员在SDK的对话框过程处理消息和事

件,实现自己的对话框功能。AfxDlgProc处理WM_INITDIALOG消息时调用虚拟函数OnInitDialog,给程序员一个机会处理对话框的初始化。

 

II.模式对话框窗口过程
   AfxWndProc是所有的MFC窗口类使用的窗口过程,它取代了模式对话框原来的窗口过程(Windows提供),那么,MFC如何完成Win32下对话框窗口的功能呢?考查模式对话框的创建过程。

CDialog::DoModal用来创建模式对话框窗口并执行有关任务,和DoModal相关的是MFC内部使用的成员函数CDialog::PreModal和CDialog::PostModal。下面分别讨论它们的实现。

[cpp]  view plain copy
  1. HWND CDialog::PreModal()  
  2. {  
  3.     ASSERT(m_hWnd == NULL);  
  4.   
  5.     // allow OLE servers to disable themselves  
  6.     AfxGetApp()->EnableModeless(FALSE);  
  7.   
  8.     // 得到父窗口  
  9.     CWnd* pWnd = CWnd::GetSafeOwner(m_pParentWnd, &m_hWndTop);  
  10.   
  11.     // 如同CWnd处理其他窗口的创建,设置一个窗口创建HOOK  
  12.     AfxHookWindowCreate(this);  
  13.   
  14.     //返回父窗口的句柄  
  15.     return pWnd->GetSafeHwnd();  
  16. }  
  17.   
  18. void CDialog::PostModal()  
  19. {  
  20.     //取消窗口创建前链接的HOOK  
  21.     AfxUnhookWindowCreate(); // just in case  
  22.   
  23.     //MFC对话框对象和对应的Windows对话框窗口分离  
  24.     Detach(); // just in case  
  25.   
  26.     // m_hWndTop是当前对话框的父窗口或所属窗口,则恢复它  
  27.     if (::IsWindow(m_hWndTop))  
  28.         ::EnableWindow(m_hWndTop, TRUE);  
  29.     m_hWndTop = NULL;  
  30.     AfxGetApp()->EnableModeless(TRUE);  
  31. }  
  32.   
  33. int CDialog::DoModal()  
  34. {  
  35.     ASSERT(m_lpszTemplateName != NULL || m_hDialogTemplate != NULL || m_lpDialogTemplate != NULL);  
  36.   
  37.     //加载对话框资源  
  38.     LPCDLGTEMPLATE lpDialogTemplate = m_lpDialogTemplate;  
  39.     HGLOBAL hDialogTemplate = m_hDialogTemplate;  
  40.     HINSTANCE hInst = AfxGetResourceHandle();  
  41.     if (m_lpszTemplateName != NULL)  
  42.     {  
  43.         hInst = AfxFindResourceHandle(m_lpszTemplateName, RT_DIALOG);  
  44.         HRSRC hResource =::FindResource(hInst, m_lpszTemplateName, RT_DIALOG);  
  45.         hDialogTemplate = LoadResource(hInst, hResource);  
  46.     }  
  47.   
  48.     //锁定加载的资源  
  49.     if (hDialogTemplate != NULL)  
  50.         lpDialogTemplate = (LPCDLGTEMPLATE)LockResource(hDialogTemplate);  
  51.     if (lpDialogTemplate == NULL)  
  52.         return -1;  
  53.   
  54.     //创建对话框前禁止父窗口,为此要调用PreModal得到父窗口句柄  
  55.     HWND hWndParent = PreModal();  
  56.     AfxUnhookWindowCreate();  
  57.     CWnd* pParentWnd = CWnd::FromHandle(hWndParent);  
  58.     BOOL bEnableParent = FALSE;  
  59.     if (hWndParent != NULL && ::IsWindowEnabled(hWndParent))  
  60.     {  
  61.         ::EnableWindow(hWndParent, FALSE);  
  62.         bEnableParent = TRUE;  
  63.     }  
  64.   
  65.     //创建对话框,注意是无模式对话框  
  66.     TRY  
  67.     {  
  68.         //链接一个HOOK到HOOK链以处理窗口创建,  
  69.         AfxHookWindowCreate(this);  
  70.   
  71.         //CreateDlgIndirect间接调用::CreateDlgIndirect,最终调用了::CreateWindowEX来创建对话框窗口。  
  72.         //HOOK过程_AfxCbtFilterHook用子类化的方法取代原来的窗口过程为AfxWndProc。  
  73.         if (CreateDlgIndirect(lpDialogTemplate, CWnd::FromHandle(hWndParent), hInst))  
  74.         {  
  75.             if (m_nFlags & WF_CONTINUEMODAL)  
  76.             {  
  77.                 // enter modal loop  
  78.                 DWORD dwFlags = MLF_SHOWONIDLE;  
  79.                 //RunModalLoop接管整个应用程序的消息处理  
  80.                 if (GetStyle() & DS_NOIDLEMSG)  
  81.                     dwFlags |= MLF_NOIDLEMSG;  
  82.                 VERIFY(RunModalLoop(dwFlags) == m_nModalResult);  
  83.             }  
  84.   
  85.             // hide the window before enabling the parent, etc.  
  86.             if (m_hWnd != NULL)  
  87.                 SetWindowPos(NULL, 0, 0, 0, 0, SWP_HIDEWINDOW|SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER);  
  88.         }  
  89.     }  
  90.   
  91.     CATCH_ALL(e)  
  92.     {  
  93.         DELETE_EXCEPTION(e);  
  94.         m_nModalResult = -1;  
  95.     }  
  96.     END_CATCH_ALL  
  97.   
  98.     //Enable并且激活父窗口  
  99.     if (bEnableParent)  
  100.         ::EnableWindow(hWndParent, TRUE);  
  101.     if (hWndParent != NULL && ::GetActiveWindow() == m_hWnd)  
  102.         ::SetActiveWindow(hWndParent);  
  103.     //::EndDialog仅仅关闭了窗口,现在销毁窗口  
  104.     DestroyWindow();  
  105.     PostModal();  
  106.   
  107.     // 必要的话,解锁/释放资源  
  108.     if (m_lpszTemplateName != NULL || m_hDialogTemplate != NULL)  
  109.         UnlockResource(hDialogTemplate);  
  110.     if (m_lpszTemplateName != NULL)  
  111.         FreeResource(hDialogTemplate);  
  112.   
  113.     return m_nModalResult;  
  114. }  

III.使用原对话框窗口过程作消息的缺省处理
   对话框的消息处理过程和其他窗口并没有什么不同。这里主要分析的是如何把一些消息传递给对话框原窗口过程处理。下面,通过解释MFC对WM_INITDIALOG消息的处理来解释MFC窗口过程

和原对话框窗口过程的关系及其协调作用。MFC提供了WM_INITDIALOG消息的处理函数CDialog::HandleInitDialog,WM_INITDIALOG消息按照标准Windows的处理送给HandleInitDialog处理。

HandleInitDialog调用缺省处理过程Default,导致CWnd的Default函数被调用。CWnd::Default的实现如下:

[cpp]  view plain copy
  1. LRESULT CWnd::Default()  
  2. {  
  3. // call DefWindowProc with the last message  
  4. _AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();  
  5. return DefWindowProc(pThreadState->m_lastSentMsg.message,pThreadState->m_lastSentMsg.wParam,pThreadState->m_lastSentMsg.lParam);  
  6. }  

顺便指出,从Default的实现可以看出线程状态的一个用途:它把本线程最新收到和处理的消息记录在成员变量m_lastSentMsg中。在Default的实现中,CWnd的DefWindowsProc被调用,其

实现如下:

[cpp]  view plain copy
  1. LRESULT CWnd::DefWindowProc(UINT nMsg,WPARAM wParam, LPARAM lParam)  
  2. {  
  3.     //若“窗口超类(SuperClass)”的窗口过程m_pfnSuper非空,则调用它  
  4.     if (m_pfnSuper != NULL)  
  5.         return ::CallWindowProc(m_pfnSuper, m_hWnd, nMsg, wParam, lParam);  
  6.   
  7.     //在MFC中,GetSuperWndProcAddr的作用就是返回m_pfnSuper,为什么还  
  8.     //要再次调用呢?因为虽然该函数现在是Obsolete,但原来曾经是有用的。  
  9.     //如果返回非空,就调用该窗口过程进行处理,否则,由Windows进行缺省处理。  
  10.     WNDPROC pfnWndProc;  
  11.     if ((pfnWndProc = *GetSuperWndProcAddr()) == NULL)  
  12.         return ::DefWindowProc(m_hWnd, nMsg, wParam, lParam);  
  13.     else  
  14.         return ::CallWindowProc(pfnWndProc, m_hWnd, nMsg, wParam, lParam);  
  15. }  

 

综合上述分析,HandleInitDialog最终调用了窗口过程m_pfnSuper,即Windows提供给“对话框窗口类”的窗口过程,于是该窗口过程调用了对话框过程AfxDlgProc,导致虚拟函数

OnInitDialog被调用。OnInitDialog的MFC缺省实现主要完成三件事情:
调用ExecInitDialog初始化对话框中的控制;调用UpdateData初始化对话框控制中的数据;确定是否显示帮助按钮。所以,程序员覆盖该函数时,一定要调用基类的实现。
MFC采用子类化的方法取代了对话框的窗口过程,原来SDK下对话框过程要处理的东西大部分转移给MFC窗口过程处理,如处理控制窗口的控制通知消息等。如果不能处理或者必须借助于原来的

窗口过程的,则通过缺省处理函数Default传递给原来的窗口过程处理,如同这里对WM_INITDIALOG的处理一样。

 

IV.Dialog命令消息和控制通知消息的处理
通过覆盖CWnd的命令消息发送函数OnCmdMsg,CDialog实现了自己的命令消息发送路径

[cpp]  view plain copy
  1. BOOL CDialog::OnCmdMsg(UINT nID, int nCode, void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo)  
  2. {  
  3.     //首先,让对话框窗口自己或者基类处理  
  4.     if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))  
  5.         return TRUE;  
  6.   
  7.     //如果还未处理,且是控制通知消息或者状态更新消息或者系统命令则停止进一步的发送  
  8.     if ((nCode != CN_COMMAND && nCode != CN_UPDATE_COMMAND_UI) ||!IS_COMMAND_ID(nID) || nID >= 0xf000)  
  9.         return FALSE; // not routed any further  
  10.   
  11.     //尝试给父窗口处理  
  12.     CWnd* pOwner = GetParent();  
  13.     if (pOwner != NULL)  
  14.     {  
  15.         ASSERT(pOwner != this);  
  16.         if (pOwner->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))  
  17.         return TRUE;  
  18.     }  
  19.   
  20.     // 最后,给当前线程对象处理  
  21.     CWinThread* pThread = AfxGetThread();  
  22.     if (pThread != NULL)  
  23.     {  
  24.         if (pThread->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))  
  25.         return TRUE;  
  26.     }  
  27.   
  28.     return FALSE;  
  29. }  

从上述实现可以看出,CDialog处理命令消息遵循如下顺序:对话框自身→父窗口→线程对象
例如,模式对话框产生的WM_ENTERIDLE消息就发送给父窗口处理。
CDialog::OnCmdMsg不仅适用于模式对话框,也适用于无模式对话框。

 

V.消息预处理和Dialog消息
   另外,对话框窗口的消息处理还有一个特点,就是增加了对Dialog消息的处理,如同在介绍::IsDialogMessage函数时所述。如果是 Dialog消息,MFC框架将不会让它进入下一步的消息循

环。为此,MFC覆盖了CDialog的虚拟函数PreTranslateMessage,该函数的实现如下:

[cpp]  view plain copy
  1. BOOL CDialog::PreTranslateMessage(MSG* pMsg)  
  2. {  
  3.     ASSERT(m_hWnd != NULL);  
  4.   
  5.     //过滤tooltip messages  
  6.     if (CWnd::PreTranslateMessage(pMsg))  
  7.         return TRUE;  
  8.   
  9.     //在Shift+F1帮助模式下,不转换Dialog messages  
  10.     CFrameWnd* pFrameWnd = GetTopLevelFrame();  
  11.     if (pFrameWnd != NULL && pFrameWnd->m_bHelpMode)  
  12.         return FALSE;  
  13.   
  14.     //处理Escape键按下的消息  
  15.     if (pMsg->message == WM_KEYDOWN &&(pMsg->wParam == VK_ESCAPE || pMsg->wParam == VK_CANCEL) &&  
  16.     (::GetWindowLong(pMsg->hwnd, GWL_STYLE) & ES_MULTILINE) &&_AfxCompareClassName(pMsg->hwnd, _T("Edit")))  
  17.     {  
  18.         HWND hItem = ::GetDlgItem(m_hWnd, IDCANCEL);  
  19.         if (hItem == NULL || ::IsWindowEnabled(hItem))  
  20.         {  
  21.             SendMessage(WM_COMMAND, IDCANCEL, 0);  
  22.             return TRUE;  
  23.         }  
  24.   
  25.     }  
  26.   
  27.     // 过滤来自控制该对话框子窗口的送给该对话框的Dialog消息  
  28.     return PreTranslateInput(pMsg);  
  29.   
  30. }  

用到的函数:

[cpp]  view plain copy
  1. BOOL CWnd::PreTranslateMessage(MSG* pMsg)  
  2. {  
  3.     // handle tooltip messages (some messages cancel, some may cause it to popup)  
  4.     AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();  
  5.     if (pModuleState->m_pfnFilterToolTipMessage != NULL)  
  6.         (*pModuleState->m_pfnFilterToolTipMessage)(pMsg, this);  
  7.   
  8.     // no default processing  
  9.     return FALSE;  
  10. }  
  11.   
  12. BOOL CWnd::PreTranslateInput(LPMSG lpMsg)  
  13. {  
  14.     ASSERT(::IsWindow(m_hWnd));  
  15.   
  16.     // don't translate non-input events  
  17.     if ((lpMsg->message < WM_KEYFIRST || lpMsg->message > WM_KEYLAST) &&  
  18.         (lpMsg->message < WM_MOUSEFIRST || lpMsg->message > AFX_WM_MOUSELAST))  
  19.         return FALSE;  
  20.   
  21.     return IsDialogMessage(lpMsg);  
  22. }  
  23.   
  24. BOOL CWnd::IsDialogMessage(LPMSG lpMsg)  
  25. {  
  26.     ASSERT(::IsWindow(m_hWnd));  
  27.   
  28.     if (m_nFlags & WF_OLECTLCONTAINER)  
  29.         return afxOccManager->IsDialogMessage(this, lpMsg);  
  30.     else  
  31.         return ::IsDialogMessage(m_hWnd, lpMsg);  
  32. }  

根据IsDialogMessage(),对话框无法处理WM_KEYUP等消息,我们可以重载PreTranslateMessage()。PreTranslateMessage的实现不仅用于模式对话框,而且用于无模式对话框。

 

VI.模式对话框的消息循环
    从DoModal的实现可以看出,DoModal调用CreateDlgIndirect创建的是无模式对话框,MFC如何来接管和控制应用程序的消息队列,实现一个模式对话框的功能呢?
CDialog调用了RunModalLoop来实现模式窗口的消息循环。RunModalLoop是CWnd的成员函数,它和相关函数的实现如下:

[cpp]  view plain copy
  1. int CWnd::RunModalLoop(DWORD dwFlags)  
  2. {  
  3.   
  4.     ASSERT(::IsWindow(m_hWnd));   
  5.     BOOL bIdle = TRUE;  
  6.     LONG lIdleCount = 0;  
  7.     BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE) &&!(GetStyle() & WS_VISIBLE);  
  8.     HWND hWndParent = ::GetParent(m_hWnd);  
  9.     m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);  
  10.     MSG* pMsg = &AfxGetThread()->m_msgCur;  
  11.   
  12.     //获取和派发消息直到模式状态结束  
  13.     for (;;)  
  14.     {  
  15.         ASSERT(ContinueModal());/  
  16.         //第一阶段,判断是否可以进行Idle处理  
  17.         while (bIdle &&!::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))  
  18.         {  
  19.             ASSERT(ContinueModal());  
  20.   
  21.             //必要的话,当Idle时显示对话框窗口  
  22.             if (bShowIdle)  
  23.             {  
  24.                 ShowWindow(SW_SHOWNORMAL);  
  25.                 UpdateWindow();  
  26.                 bShowIdle = FALSE;  
  27.             }  
  28.   
  29.             //进行Idle处理,必要的话发送WM_ENTERIDLE消息给父窗口  
  30.             if (!(dwFlags & MLF_NOIDLEMSG) &&hWndParent != NULL && lIdleCount == 0)  
  31.             {  
  32.                 ::SendMessage(hWndParent, WM_ENTERIDLE,  
  33.                 MSGF_DIALOGBOX, (LPARAM)m_hWnd);  
  34.             }  
  35.   
  36.             //必要的话发送WM_KICKIDLE消息给父窗口  
  37.             if ((dwFlags & MLF_NOKICKIDLE) ||!SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))  
  38.             {  
  39.                 //终止Idle处理  
  40.                 bIdle = FALSE;  
  41.             }  
  42.         }  
  43.   
  44.         //第二阶段,发送消息  
  45.         do  
  46.         {  
  47.             ASSERT(ContinueModal());  
  48.             // 若是WM_QUIT消息,则发送该消息到消息队列,返回;否则发送消息。  
  49.             if (!AfxGetThread()->PumpMessage())  
  50.             {  
  51.                 AfxPostQuitMessage(0);  
  52.                 return -1;  
  53.             }  
  54.   
  55.             //必要的话,显示对话框窗口  
  56.             if (bShowIdle &&(pMsg->message == 0x118 || pMsg->message == WM_SYSKEYDOWN))  
  57.             {  
  58.                 ShowWindow(SW_SHOWNORMAL);  
  59.                 UpdateWindow();  
  60.                 bShowIdle = FALSE;  
  61.             }  
  62.   
  63.             if (!ContinueModal())  
  64.             goto ExitModal;  
  65.   
  66.             //在派发了“正常 ”消息后,重新开始Idle处理  
  67.             if (AfxGetThread()->IsIdleMessage(pMsg))  
  68.             {  
  69.                 bIdle = TRUE;  
  70.                 lIdleCount = 0;  
  71.             }  
  72.         } while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE));  
  73.     }  
  74.   
  75.     ExitModal:  
  76.     m_nFlags &= ~(WF_MODALLOOP|WF_CONTINUEMODAL);  
  77.     return m_nModalResult;  
  78. }  

     和CWinThread::Run的处理过程比较,RunModalLoop也分两个阶段进行处理。这里不同于Run的Idle处理,RunModalLoop是给父窗口发送WM_ENTERIDLE消息(如果需要的话);另外,当前对

话框的父窗口被Disabled,是不接收用户消息的。RunModalLoop是一个实现自己的消息循环的示例,消息循环的条件是模式化状态没有结束。实现线程自己的消息循环见8.5.6节。当用户按下

按钮“取消”、“确定”时,将导致RunModalLoop退出消息循环,结束对话框模式状态,并调用::EndDialog关闭窗口。
OnOk、OnCancle、EndDialog都可以用来关闭对话框窗口。其中:
OnOk首先进行数据交换,获取对话框中各个控制子窗口的数据,然后调用EndDialog结束对话框。
OnCancle直接EndDialog结束对话框。

 

三.数据交换
   对话框数据交换指以下两种动作,或者是把内存数据写入对应的控制窗口,或者是从控制窗口读取数据并保存到内存变量中。MFC为了简化这些操作,以CDataExchange类和一些数据交换函

数为基础,提供了一套数据交换和校验的机制。
相应的DoDataExchange的实现如下:

[cpp]  view plain copy
  1. void CExDialog::DoDataExchange(CDataExchange* pDX)  
  2. {  
  3.     CDialog::DoDataExchange(pDX);  
  4.     DDX_Control(pDX, IDC_NAME, m_name);  
  5.     DDX_Text(pDX, IDC_AGE, m_nAge);  
  6.     DDV_MinMaxInt(pDX, m_nAge, 1, 100);  
  7. }  

DDX_ Control表示把IDC_NAME子窗口的内容传输到窗口m_name,或者相反。
DDX_ Text表示把IDC_AGE子窗口的内容按整数类型保存到m_nAge,或者相反。
DDV_MinMaxInt表示m_nAge应该在1和100之间取值

 

1.CDataExchange类

[cpp]  view plain copy
  1. class CDataExchange  
  2. {  
  3. public:  
  4.   
  5.     BOOL m_bSaveAndValidate; // TRUE 则 保存和验证数据  
  6.     CWnd* m_pDlgWnd; // 指向一个对话框  
  7.   
  8.     HWND PrepareCtrl(int nIDC); //返回指定ID的控制窗口的句柄  
  9.     HWND PrepareEditCtrl(int nIDC); //返回指定ID的编辑控制窗口句柄  
  10.     void Fail(); // 用来扔出例外  
  11.   
  12.     CDataExchange(CWnd* pDlgWnd, BOOL bSaveAndValidate);  
  13.     HWND m_hWndLastControl; // last control used (for validation)  
  14.     BOOL m_bEditLastControl; // last control was an edit item  
  15. };  

DoDataExchange类似于Serialize函数,CDataExchange类似于 CArchive。CDataExchange使用成员变量m_pDlgWnd保存要进行数据交换的对话框,使用成员变量 m_bSaveAndValidate指示数

据传输的方向,如果该变量真,则从控制窗口读取数据到成员变量,如果假,则从成员变量写数据到控制窗口。
在构造一个CDataExchange对象时,将保存有关信息在对象的成员变量中。构造函数如下:

[cpp]  view plain copy
  1. CDataExchange::CDataExchange(CWnd* pDlgWnd, BOOL bSaveAndValidate)  
  2. {  
  3.     ASSERT_VALID(pDlgWnd);  
  4.     m_bSaveAndValidate = bSaveAndValidate;  
  5.     m_pDlgWnd = pDlgWnd;  
  6.     m_hWndLastControl = NULL;  
  7. }  

 

2.数据交换和验证函数
在进行数据交换或者验证时,首先使用PrePareCtrl或者PrePareEditCtrl得到控制窗口的句柄,然后使用::GetWindowsText从控制窗口读取数据,或者使用::SetWindowsText写入数据到控制

窗口。下面讨论几个例子:

[cpp]  view plain copy
  1. static void AFX_CDECL DDX_TextWithFormat(CDataExchange* pDX,int nIDC,LPCTSTR lpszFormat, UINT nIDPrompt, ...)  
  2. {  
  3.     va_list pData; //用来处理个数可以变化的参数  
  4.     va_start(pData, nIDPrompt);//得到参数  
  5.   
  6.     //得到编辑框的句柄  
  7.     HWND hWndCtrl = pDX->PrepareEditCtrl(nIDC);  
  8.     TCHAR szT[32];  
  9.     if (pDX->m_bSaveAndValidate) //TRUE,从编辑框读出数据  
  10.     {  
  11.         //从编辑框得到内容  
  12.         ::GetWindowText(hWndCtrl, szT, _countof(szT));  
  13.         //转换编辑框内容为指定的格式,支持“ %d, %u, %ld, %lu”  
  14.         if (!AfxSimpleScanf(szT, lpszFormat, pData))  
  15.         {  
  16.             AfxMessageBox(nIDPrompt);  
  17.             pDX->Fail(); //数据交换失败  
  18.         }  
  19.     }  
  20.     else //FALSE,写入数据到编辑框  
  21.     {  
  22.         //把要写的内容转换成指定格式,不支持浮点运算  
  23.         wvsprintf(szT, lpszFormat, pData);  
  24.         //设置编辑框的内容  
  25.         AfxSetWindowText(hWndCtrl, szT);  
  26.     }  
  27.   
  28.     va_end(pData);//结束参数分析  
  29. }  
  30.   
  31. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, long& value)  
  32. {  
  33.     if (pDX->m_bSaveAndValidate)  
  34.         DDX_TextWithFormat(pDX, nIDC, _T("%ld"), AFX_IDP_PARSE_INT, &value);  
  35.     else  
  36.         DDX_TextWithFormat(pDX, nIDC, _T("%ld"), AFX_IDP_PARSE_INT, value);  
  37. }  

 

3.UpdateData函数

[cpp]  view plain copy
  1. BOOL CWnd::UpdateData(BOOL bSaveAndValidate)  
  2. {  
  3.     ASSERT(::IsWindow(m_hWnd));   
  4.   
  5.     //创建CDataChange对象  
  6.     CDataExchange dx(this, bSaveAndValidate);  
  7.     DoDataExchange(&dx);  
  8.     //.................  
  9.     return bOK;  
  10. }  

 

四,模式对话框的使用
CFormView是MFC使用无模式对话框的一个典型例子。CFormView是基于对话框模板创建的视,它的直接基类是CSrcollView,CSrcollView的直接基类才是CView。
这样不做详细介绍了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值