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