?????大家在編程的過程中一定遇到過這種情況:需要根據(jù)某個變量的值來設定菜單項是否被選中,設置工具欄按鈕是否被按下或者在狀態(tài)欄中顯示一些信息。
MFC
提供了一種機制來幫助我們完成這項工作:只要用
ClassWizard
給相應的菜單項或者工具欄按鈕添加一個
UPDATE_COMMAND_UI
處理函數(shù),在其中用
CcmdUI::SetCheck
等函數(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消息,如果你不響應這個消息,說明這個菜單項還沒有處理函數(shù),那么,bnHandler就是flase,然后Enable(false)就把你的菜單項變灰了。注意在CFrameWnd::OnInitMenuPopup中調(diào)用DoUpdate時的參數(shù)是m_bAutoMenuEnable && state.m_nID<0xF000,這說如果你一開始就把m_bAutoMenuEnable設為false的話,實際上就關閉了MFC自動diable沒有處理函數(shù)的菜單項的功能。
?? 工具欄的更新用的是另外一套方法。首先需要知道當你的的程序變得空閑,沒有消息需要處理的時候,
MFC
會調(diào)用
CWinApp::OnIdle
函數(shù)利用這個時間進行一些特殊的工作,其中之一就是更新你的工具欄和狀態(tài)欄。下面來看相關的代碼:
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)驗的朋友應該知道,如果你在一個基于對話框的程序里模仿
doc/view
結構中的方法使用
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