weidagang2046的專欄

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

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

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

          ??? 首先讓我們來看看菜單狀態更新的實現方法。首先要知道,當你點現了一個有子菜單的菜單項時 ( 比如菜單欄上的“文件” ) ,系統會向擁有這個菜單的窗口發送一個 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 的流程就是:先向你的菜單項發一個 CN_UPDATE_COMMAND_UI 命令消息,讓你的菜單項來進行顯示前的更新,這就是你在classwizard中可以看到的UPDATE_COMMADN_UI消息,你加的處理函數就是在這個時候被調用的。如果你處理了CN_UPDATE_COMMAND_UI,那么m_bEnableChanged就變成true,接下來就直接返回了。否則,如果bDisableIfNoHndler也為true,那么就向菜單項發一個CN_COMMAND消息,如果你不響應這個消息,說明這個菜單項還沒有處理函數,那么,bnHandler就是flase,然后Enable(false)就把你的菜單項變灰了。注意在CFrameWnd::OnInitMenuPopup中調用DoUpdate時的參數是m_bAutoMenuEnable && state.m_nID<0xF000,這說如果你一開始就把m_bAutoMenuEnable設為false的話,實際上就關閉了MFC自動diable沒有處理函數的菜單項的功能。

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

          BOOL CWinThread::OnIdle(LONG lCount){

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

          // 依次向 main window 及其所有子窗口發送 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);

          ??????? }

          // 接下來向本線程創建的所有 frame window 發送 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();

          ????????????? // 調用虛成員函數 OnUpdateCmdUI

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

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

          ?????? }

          ?????? return 0L;

          }

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

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

          ?????? CToolCmdUI state;?

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

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

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

          ?????? ? // 如果你派生了自己的 CToolBar 類,那么先讓執行你定義的處理函數來進行狀態更新

          ?????? ? 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 中有用戶創建的控件,也一起更新

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

          }

          CCmdUI::DoUpdate 的代碼上面已經列出過了。至此,工具欄和狀態欄也能順利也進行更了。

          有經驗的朋友應該知道,如果你在一個基于對話框的程序里模仿 doc/view 結構中的方法使用 UPDATE_COMMAND_UI 來更新用戶界面元素的話是不會有任何效果的。其原因是一個模態對話顯示出來以后,程序就會進入這個對話框自己的消息循環 ( 看看 DoModal 的源碼就能了解這一點 ) ,此時不會再有 WM_IDLEUPDATECMDUI 被發送到這些界面元素中。下面說說這種情況下的解決辦法,你可以自己查看 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 閱讀(1197) 評論(0)  編輯  收藏 所屬分類: Windows

          主站蜘蛛池模板: 海阳市| 石柱| 托克逊县| 东乡族自治县| 湘潭县| 武冈市| 无为县| 乌鲁木齐县| 元谋县| 陵川县| 武宁县| 宜君县| 绿春县| 花莲县| 清水河县| 武陟县| 车险| 工布江达县| 武川县| 廊坊市| 翼城县| 洮南市| 嘉义市| 宜川县| 云浮市| 冷水江市| 新乡县| 凤城市| 丹东市| 苗栗市| 永福县| 时尚| 宜城市| 丰镇市| 龙岩市| 宁夏| 吴堡县| 双城市| 上栗县| 合川市| 屏东县|