weidagang2046的專欄

          物格而后知致
          隨筆 - 8, 文章 - 409, 評論 - 101, 引用 - 0
          數(shù)據(jù)加載中……

          MFC中用戶界面元素更新原理

          ?????大家在編程的過程中一定遇到過這種情況:需要根據(jù)某個變量的值來設(shè)定菜單項是否被選中,設(shè)置工具欄按鈕是否被按下或者在狀態(tài)欄中顯示一些信息。 MFC 提供了一種機制來幫助我們完成這項工作:只要用 ClassWizard 給相應(yīng)的菜單項或者工具欄按鈕添加一個 UPDATE_COMMAND_UI 處理函數(shù),在其中用 CcmdUI::SetCheck 等函數(shù)來設(shè)置這些用戶界面元素的狀態(tài)就可以了。但是 MFC 是怎么實現(xiàn)這個功能的呢?

          ??? 首先讓我們來看看菜單狀態(tài)更新的實現(xiàn)方法。首先要知道,當你點現(xiàn)了一個有子菜單的菜單項時 ( 比如菜單欄上的“文件” ) ,系統(tǒng)會向擁有這個菜單的窗口發(fā)送一個 WM_INITMENUPOPUP ,下面是 MFC 對這個消息的默認處理: void CFrameWnd::OnInitMenuPopup(CMenu* pMenu, UINT nIndex, BOOL bSysMenu) {

          ??? // 為了說明問題,我省略了很多代碼 ???

          ??? CCmdUI state;

          ???? state.m_pMenu = pMenu;?????? ??????

          ???? state.m_nIndexMax = pMenu->GetMenuItemCount();

          ???? for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax;state.m_nIndex++){

          ?????? ???? state.m_nID = pMenu->GetMenuItemID(state.m_nIndex);

          ?????? ???? if (state.m_nID == (UINT)-1)

          ?????? ???? {

          ????????????? //m_nID==-1 表示它下面還有 popup menu( 就那種帶右箭頭的菜單項 )

          ????????????? // 它是不會自動 deisable ????? ?????????????

          ?????? ???? }

          ?????? ???? else

          ?????? ???? {

          ?????? ????? ??? ?state.m_pSubMenu = NULL;

          ?????? ???? ??? ??state.DoUpdate(this, m_bAutoMenuEnable && state.m_nID < 0xF000);

          ?????? ???? }

          }

          下面是 CCmdUI::DoUpdate 的代碼:

          BOOL CCmdUI::DoUpdate(CCmdTarget* pTarget, BOOL bDisableIfNoHndler){

          ???? m_bEnableChanged = FALSE;

          BOOL bResult=pTarget->OnCmdMsg(m_nID,CN_UPDATE_COMMAND_UI,

          this, NULL);

          ???? if (bDisableIfNoHndler && !m_bEnableChanged){

          ?????? ???? AFX_CMDHANDLERINFO info;

          ?????? ???? info.pTarget = NULL;

          ?????? ???? BOOL bHandler = pTarget->OnCmdMsg(m_nID, CN_COMMAND, this, &info);

          ?????? ???? Enable(bHandler);

          ???? }

          ????? return bResult;

          }

          DoUpdate 的流程就是:先向你的菜單項發(fā)一個 CN_UPDATE_COMMAND_UI 命令消息,讓你的菜單項來進行顯示前的更新,這就是你在classwizard中可以看到的UPDATE_COMMADN_UI消息,你加的處理函數(shù)就是在這個時候被調(diào)用的。如果你處理了CN_UPDATE_COMMAND_UI,那么m_bEnableChanged就變成true,接下來就直接返回了。否則,如果bDisableIfNoHndler也為true,那么就向菜單項發(fā)一個CN_COMMAND消息,如果你不響應(yīng)這個消息,說明這個菜單項還沒有處理函數(shù),那么,bnHandler就是flase,然后Enable(false)就把你的菜單項變灰了。注意在CFrameWnd::OnInitMenuPopup中調(diào)用DoUpdate時的參數(shù)是m_bAutoMenuEnable && state.m_nID<0xF000,這說如果你一開始就把m_bAutoMenuEnable設(shè)為false的話,實際上就關(guān)閉了MFC自動diable沒有處理函數(shù)的菜單項的功能。

          ?? 工具欄的更新用的是另外一套方法。首先需要知道當你的的程序變得空閑,沒有消息需要處理的時候, MFC 會調(diào)用 CWinApp::OnIdle 函數(shù)利用這個時間進行一些特殊的工作,其中之一就是更新你的工具欄和狀態(tài)欄。下面來看相關(guān)的代碼:

          BOOL CWinThread::OnIdle(LONG lCount){

          ?????? if (lCount <= 0){

          // 依次向 main window 及其所有子窗口發(fā)送 WM_IDLEUPDATECMDUI 消息,這個 消息指示接收窗口進行更新操作

          ????????????? CWnd* pMainWnd = m_pMainWnd;

          ????????????? if (pMainWnd != NULL && pMainWnd->m_hWnd != NULL &&

          ????????????? ?????? pMainWnd->IsWindowVisible())

          ????????????? {

          ????????????? ?????? AfxCallWndProc(pMainWnd, pMainWnd->m_hWnd,

          ???????????????????? ?????? WM_IDLEUPDATECMDUI, (WPARAM)TRUE, 0);

          ????????????? ?????? pMainWnd-> SendMessageToDescendants

          ??????????????????????? ( WM_IDLEUPDATECMDUI, (WPARAM)TRUE, 0, TRUE, TRUE);

          ??????? }

          // 接下來向本線程創(chuàng)建的所有 frame window 發(fā)送 WM_IDLEUPDATECMDUI 消息

          ?????? ?????? AFX_MODULE_THREAD_STATE*? pState=

          _AFX_CMDTARGET_GETSTATE()->m_thread;

          ?????? ?????? CFrameWnd* pFrameWnd = pState->m_frameList;

          ????????????? while (pFrameWnd != NULL){

          ???????????????????? ?if (pFrameWnd->IsWindowVisible()||pFrameWnd->m_nShowDelay >= 0){

          ???????????????????? ?????? ?????? AfxCallWndProc(pFrameWnd, pFrameWnd->m_hWnd,

          ???????????????????? ????????????? ?????? WM_IDLEUPDATECMDUI, (WPARAM)TRUE, 0);

          ???????????????????? ?pFrameWnd->SendMessageToDescendants(WM_IDLEUPDATECMDUI,

          ???????????????????? ????????????? ?????? (WPARAM)TRUE, 0, TRUE, TRUE);

          ???????????????????? ?????? }

          ????????????? }

          ?????? }

          }

          你的 toolbar 或者 statusbar 總是某個 frame window 的子窗口 ( 包括子窗口的子窗口… ) ,所以它肯定能收到 WM_IDLEUPDATECMDUI 消息。 CToolBar CStatusBar 都是從 CControlBar 派生的,下面是 CControlBar 對這個消息的處理:

          LRESULT CControlBar::OnIdleUpdateCmdUI(WPARAM wParam, LPARAM)

          {

          ?????? if ((GetStyle() & WS_VISIBLE) )

          ?????? {

          ?????? ?? // pTarget 指向離 this 最近的父 frame window

          ?????? ?????? CFrameWnd* pTarget = (CFrameWnd*)GetOwner();

          ????????????? if (pTarget == NULL || !pTarget->IsFrameWnd())

          ????????????? ?????? pTarget = GetParentFrame();

          ????????????? // 調(diào)用虛成員函數(shù) OnUpdateCmdUI

          ????????????? if (pTarget != NULL)

          ????????????? ?????? OnUpdateCmdUI(pTarget, (BOOL)wParam);

          ?????? }

          ?????? return 0L;

          }

          OnUpdateCmdUI CControlBar 類的一個純虛函數(shù), CToolBar 中對這個函數(shù)進行了定義:

          void CToolBar::OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler){

          ?????? CToolCmdUI state;?

          ?????? state.m_pOther = this;

          ?????? state.m_nIndexMax = DefWindowProc(TB_BUTTONCOUNT, 0, 0); // 工具欄上的按鈕數(shù)

          ?????? for (state.m_nIndex=0; state.m_nIndex < state.m_nIndexMax; state.m_nIndex++){

          ?????? ? // 如果你派生了自己的 CToolBar 類,那么先讓執(zhí)行你定義的處理函數(shù)來進行狀態(tài)更新

          ?????? ? if (CWnd::OnCmdMsg(state.m_nID, CN_UPDATE_COMMAND_UI, &state, NULL))

          ???????????????????? ?????? continue;

          // 如果 toolbar 沒有更新自己,讓 pTarget( 也就是離它最近的父 frame window) 來更新它。 比如對于 MFC 自動生成的 SDI 框架來說, pTarget 會指向 CMainFrame

          ????????????? ?????? state.DoUpdate(pTarget, bDisableIfNoHndler);

          ????????????? }

          ?????? }

          ?????? // 如果 CToolBar 中有用戶創(chuàng)建的控件,也一起更新

          ?????? UpdateDialogControls(pTarget, bDisableIfNoHndler);

          }

          CCmdUI::DoUpdate 的代碼上面已經(jīng)列出過了。至此,工具欄和狀態(tài)欄也能順利也進行更了。

          有經(jīng)驗的朋友應(yīng)該知道,如果你在一個基于對話框的程序里模仿 doc/view 結(jié)構(gòu)中的方法使用 UPDATE_COMMAND_UI 來更新用戶界面元素的話是不會有任何效果的。其原因是一個模態(tài)對話顯示出來以后,程序就會進入這個對話框自己的消息循環(huán) ( 看看 DoModal 的源碼就能了解這一點 ) ,此時不會再有 WM_IDLEUPDATECMDUI 被發(fā)送到這些界面元素中。下面說說這種情況下的解決辦法,你可以自己查看 MFC 的源碼來弄清它的原理:首先加一個頭文件 afxpriv.h(其中定義了KICKIDLE消息) ,然后添加一個消息映射來處理 WM_KICKIDLE消息:ON_MESSAGE(WM_KICKIDLE,OnKickIdle)。其中OnKickIdle定義如下:

          LRESULT CTabDialog::OnKickIdle(WPARAM wp, LPARAM lCount){

          ?UpdateDialogControls(this, TRUE);

          return 0;

          }

          完成這些工作以后 , 你就可以順利地使用 UPDATE_COMMAND_UI 機制了。

          from: http://www.zahui.com/html/1/2881.htm

          posted on 2006-08-29 16:57 weidagang2046 閱讀(1198) 評論(0)  編輯  收藏 所屬分類: Windows

          主站蜘蛛池模板: 岢岚县| 华容县| 石景山区| 奎屯市| 乌鲁木齐县| 麻江县| 大田县| 墨脱县| 汨罗市| 灵山县| 开鲁县| 安福县| 阜康市| 昔阳县| 潢川县| 乐昌市| 徐汇区| 上高县| 宝应县| 洛宁县| 石狮市| 浙江省| 洛浦县| 彰化市| 锦屏县| 济宁市| 福州市| 吴江市| 报价| 个旧市| 蓬溪县| 江都市| 玛沁县| 南城县| 南涧| 威海市| 思茅市| 本溪市| 泰安市| 嘉禾县| 礼泉县|