Windows (Windows95或者以上版本) 提供了系列通用控制窗口,其中包括工具條(ToolBar)、狀態(tài)欄(StatusBar)、工具條提示窗口(ToolTip)。
Windows在一個(gè)DLL加載時(shí)注冊(cè)個(gè)控制窗口的“窗口類”。例如,工具條的“窗口類”是“ToolbarWindow32”,狀態(tài)欄的“窗口類”是“msctls_statusbar32”,工具條提示窗口的“窗口類”是“tooltips_class32”。為了保證該DLL被加載,使用控制“窗口類”前,應(yīng)該首先調(diào)用函數(shù)InitCommonControl。MFC在窗口注冊(cè)函數(shù)AfxDeferRegisterClass中實(shí)現(xiàn)了這一點(diǎn)。見2.2.1節(jié)MFC下窗口的注冊(cè)。
創(chuàng)建通用控制窗口,可以使用專門的創(chuàng)建函數(shù),如創(chuàng)建工具條的函數(shù)::CreateToolBarEx,創(chuàng)建狀態(tài)欄的函數(shù)::CreateStatusBarEx。也可以調(diào)用窗口創(chuàng)建函數(shù)::CreateWindowEx,但是需要指定預(yù)定義的“窗口類”,必要的話還要其他步驟,如使用“ToolbarWindow32”“窗口類”創(chuàng)建工具欄后,還需要在工具欄中添加或者插入按鈕。
一般,通用控制可以指定控制窗口風(fēng)格(Style)。例如,具備風(fēng)格CCS_TOP,表示該控制窗口放到父窗口客戶區(qū)的頂部,具備CCS_BOTTOM,表示該控制窗口在客戶區(qū)的底部。具體的控制窗口類可以有特別的適合于自己的風(fēng)格,例如,TTS_ALWAYSTIP表示只要光標(biāo)落在工具欄的按鈕上,ToolTip窗口不論激活與否都會(huì)顯示出來。
每一控制窗口類都有自己的窗口過程來處理自己的窗口消息,實(shí)現(xiàn)特定的功能??刂拼翱陬惖拇翱谶^程由Windows提供。
工具條的窗口過程處理了必要的消息,提供了標(biāo)準(zhǔn)工具條的功能,例如,工具條對(duì)客戶化特征提供內(nèi)在的支持,用戶可以通過一個(gè)客戶化對(duì)話框來添加、修改、刪除或者重新安排工具條按鈕。這些特征是否可以被用戶所用或者用到什么地步是可以由程序控制的。
工具條的窗口過程將自動(dòng)設(shè)置工具條的尺寸大小和位置,如果指定了控制窗口風(fēng)格CCS_TOP或者CCS_BOTTOM,則窗口過程把工具條放到父窗口客戶區(qū)的頂部或者底部。窗口過程任何時(shí)候只要收到WM_SIZE或者TB_AUTOSIZE消息就自動(dòng)地調(diào)整工具條的大小和位置。
工具條的按鈕被選中后,會(huì)產(chǎn)生一個(gè)命令消息,它的窗口過程把該消息送給父窗口的窗口過程處理。
工具條中的按鈕并不以子窗口的形式出現(xiàn),而是以字符或者位圖按鈕的方式顯示,每個(gè)按鈕大小相同,缺省是24*22個(gè)像素。每個(gè)按鈕都有一個(gè)索引,索引編號(hào)從0開始。每個(gè)按鈕包括如下屬性:
按鈕的字符串索引,位圖索引,風(fēng)格,狀態(tài),命令I(lǐng)D
按鈕可以有兩種風(fēng)格TBSTYLE_BUTTON和TBSTYLE_CHECK,前者像一個(gè)標(biāo)準(zhǔn)按鈕那樣響應(yīng)用戶的按擊,后者響應(yīng)每一次按擊,在按下和跳起兩種狀態(tài)之間切換。按鈕響應(yīng)用戶的動(dòng)作,給父窗口發(fā)送一個(gè)包含了該按鈕對(duì)應(yīng)命令I(lǐng)D的命令消息。一般一個(gè)按鈕的命令I(lǐng)D對(duì)應(yīng)一個(gè)菜單項(xiàng)。
工具條維護(hù)兩個(gè)列表,分別用來存放工具條按鈕使用的字符串或者位圖,列表中的位圖或者字符串從0開始編號(hào),編號(hào)和按鈕的索引相對(duì)應(yīng)。
工具條可以是Dockable(泊位)或者Floatable(漂浮)的。
工具條可以有TBSTYLE_TOOLTIPS風(fēng)格,如果具有這種風(fēng)格,則創(chuàng)建和管理一個(gè)Tooltip控制,這是一個(gè)小的彈出式窗口,用來顯示描述按鈕的文本,平時(shí)該窗口隱藏,當(dāng)鼠標(biāo)落到按鈕上面并停留約一秒后才彈出,在鼠標(biāo)附近顯示。
由于Tooltip窗口平時(shí)是隱藏的,所以不能接收鼠標(biāo)消息來決定何時(shí)顯示本窗口。這樣,接收鼠標(biāo)的窗口必須把鼠標(biāo)消息送給Tooltip窗口,這是通過給Tooptip窗口發(fā)送消息TTM_RELAYEVENT來實(shí)現(xiàn)的。
狀態(tài)欄類似于工具條,有自己的窗口過程,可以泊位、漂浮。不過,習(xí)慣上狀態(tài)欄都位于屏幕底部。每個(gè)狀態(tài)條分成若干格(Status bar panes),每格從0開始編號(hào),編號(hào)作為格的索引。每一個(gè)格,如同工具條的按鈕一樣,并不是一個(gè)Windows窗口。
MFC使用CToolBarCtrl、CStatusBarCtrl和CToolTipCtrl窗口類分別對(duì)工具條、狀態(tài)欄、Tooltip控制窗口進(jìn)行了封裝。
但是,直接使用這些類還不是很方便。MFC提供了CToolBar、CStatusBar來處理狀態(tài)欄和工具條,CToolBar、CStatusBar功能更強(qiáng)大,靈活。這兩個(gè)類都派生于CControlBar。
在MFC下,建議這些控制條子窗口ID介于AFX_IDW_TOOLBARFIRST(0xE800)和AFX_IDW_CONTROLBAR_LAST(0Xe8FF)之間。這256個(gè)ID中,前32個(gè)又有其特殊性,用于MFC的打印預(yù)覽中。
CControlBar派生于CWnd類,是控制條窗口類的基類,它派生出CToolBar、CStatusBar、CDockBar、CDialogBar、COleResizeBar類。CControlBar實(shí)現(xiàn)了以下功能:
CStatusBar和CControlBar一方面建立在CControlBar的基礎(chǔ)之上,另一方面以Windows的通用控制狀態(tài)欄和工具條為基礎(chǔ)。它們繼承了CControlBar類的特性,但是所封裝的窗口句柄是相應(yīng)的Windows控制窗口的句柄,如同CFormView繼承了CSrcollView的視類特性,但是其窗口句柄是無模式對(duì)話框窗口句柄一樣。
典型地,如果在使用AppWizard生成應(yīng)用程序時(shí),指定了要求工具條和狀態(tài)欄的支持,則在主邊框窗口的OnCreate函數(shù)中包含一段如下的代碼,用來創(chuàng)建工具條、狀態(tài)欄和設(shè)置一些特性。
//創(chuàng)建工具欄
if (!m_wndToolBar.Create(this) ||!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
//創(chuàng)建狀態(tài)欄
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
// TODO: Remove this if you don't want tool tips or a resizeable toolbar
//對(duì)工具欄設(shè)置Tooltip特征
m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
//使得工具欄可以泊位在邊框窗口
// TODO: Delete these three lines if you don't want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
工具條除了Tooltip,Resizeable,Dockable特性外,還可以是Floatable。應(yīng)用程序可以使用CFrameWnd::SaveBarState保存邊框窗口的控制條的有關(guān)信息到INI文件或者Windows Register庫,使用LoadBarSate從INI文件或者Register庫中讀取有關(guān)信息并恢復(fù)各個(gè)控制條的設(shè)置。
下文,將討論工具條等的創(chuàng)建、銷毀,從中分析CControlBar和派生類的關(guān)系,討論CControlBar如何實(shí)現(xiàn)共性,如何支持派生類的特定要求,派生類又如何實(shí)現(xiàn)自己的特定需求等。
創(chuàng)建工具條、狀態(tài)條、對(duì)話框工具欄的方法是不同的,所以必須給每個(gè)派生類CToolBar、CStatusBar、CDialogBar設(shè)計(jì)和實(shí)現(xiàn)自己的窗口創(chuàng)建函數(shù)Create。但是,它們是也是有共性的,共性由CControlBar的PreCreateWindow處理。在窗口創(chuàng)建之后,各個(gè)派生類都要進(jìn)行的處理(共性)由CControlBar的OnCreate完成,特別的處理通過派生類的OnNcCreate完成。
首先,討論CControlBar 類的PreCreateWindow的實(shí)現(xiàn)。
BOOL CControlBar::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CWnd::PreCreateWindow(cs))
return FALSE;
//修改窗口風(fēng)格,強(qiáng)制適用clipsliblings,以防重復(fù)繪制
cs.style |= WS_CLIPSIBLINGS;
//default border style translation for Win4
//(you can turn off this translation by setting CBRS_BORDER_3D)
if (afxData.bWin4 && (m_dwStyle & CBRS_BORDER_3D) == 0)
{
DWORD dwNewStyle = 0;
switch (m_dwStyle & (CBRS_BORDER_ANY|CBRS_ALIGN_ANY))
{
case CBRS_LEFT: //控制條在邊框窗口的左邊顯示
dwNewStyle = CBRS_BORDER_TOP|CBRS_BORDER_BOTTOM;
break;
case CBRS_TOP://控制條在邊框窗口的頂部顯示
dwNewStyle = CBRS_BORDER_TOP;
break;
case CBRS_RIGHT://控制條在邊框窗口的右邊顯示
dwNewStyle = CBRS_BORDER_TOP|CBRS_BORDER_BOTTOM;
break;
case CBRS_BOTTOM://控制條在邊框窗口的底部顯示
dwNewStyle = CBRS_BORDER_BOTTOM;
break;
}
// set new style if it matched one of the predefined border types
if (dwNewStyle != 0)
{
m_dwStyle &= ~(CBRS_BORDER_ANY);
m_dwStyle |= (dwNewStyle | CBRS_BORDER_3D);
}
}
return TRUE;
}
其中,afxData是一個(gè)全局變量,MFC用它來記錄系統(tǒng)信息,如版本信息等。這里afxData.bWin4表示W(wǎng)indows版本是否高于4.0。
CToolBar的PreCreateWindow函數(shù)修改了窗口風(fēng)格,也修改狀態(tài)欄、工具欄等的CBRS_風(fēng)格。CBRS_風(fēng)格的改變不會(huì)影響窗口風(fēng)格。因?yàn)檫@些CBRS_風(fēng)格被保存在成員變量m_dwStyle中。
除了上述在程序中用到的影響工具條、狀態(tài)欄等顯示位置的CBRS_風(fēng)格外,還有和泊位相關(guān)的CBRS_風(fēng)格,CBRS_ALIGN_LEFT、CBRS_ALIGN_RIGHT、CBRS_ALIGN_BOTTOM、CBRS_ALIGN_TOP、CBRS_ALIGN_ANY,分別表示工具條可以在停泊在邊框窗口的左邊、右邊、底部、頂部或者所有這些位置;和漂浮相關(guān)的CBRS_風(fēng)格CBRS_FLOAT_MULTI,表示多個(gè)工具條可以漂浮在一個(gè)微型邊框窗口中;和Tooltips相關(guān)的CBRS_風(fēng)格CBRS_TOOLTIPS和CBRS_FLYBY。
派生類如果沒有特別的要求,可以不覆蓋PreCreateWindow函數(shù)。CStatusBar因?yàn)橛懈唧w和特殊的風(fēng)格要求,所以它覆蓋了PreCreateWindow。CStatusBar的覆蓋實(shí)現(xiàn)調(diào)用了CControlBar的實(shí)現(xiàn)。
派生類也可以在覆蓋實(shí)現(xiàn)中修改PreCreateWindow參數(shù)cs,改變窗口風(fēng)格;修改m_dwStyle,改變CBRS_風(fēng)格。
CControlBar派生類實(shí)現(xiàn)了自己的窗口創(chuàng)建函數(shù)Create,CControlBar的PreCreateWindow被派生類的Create函數(shù)直接或者間接地調(diào)用。以CToolBar為例討論窗口創(chuàng)建函數(shù)和創(chuàng)建過程。
Create函數(shù)實(shí)現(xiàn)如下:
BOOL CToolBar::Create(CWnd* pParentWnd, DWORD dwStyle, UINT nID)
{
ASSERT_VALID(pParentWnd); // must have a parent
ASSERT (!((dwStyle & CBRS_SIZE_FIXED) &&
(dwStyle & CBRS_SIZE_DYNAMIC)));
// 保存dwStyle指定的CBRS_風(fēng)格
m_dwStyle = dwStyle;
if (nID == AFX_IDW_TOOLBAR)
m_dwStyle |= CBRS_HIDE_INPLACE;
//去掉參數(shù)dwStyle包含的CBRS_風(fēng)格
dwStyle &= ~CBRS_ALL;
//設(shè)置窗口風(fēng)格
dwStyle |=
CCS_NOPARENTALIGN|CCS_NOMOVEY|CCS_NODIVIDER|CCS_NORESIZE;
//初始化通用控制,可以導(dǎo)致InitCommonControl的調(diào)用
VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTLS_REG));
//創(chuàng)建窗口,將調(diào)用PreCreateWindow,OnCreate, OnNcCreate等
CRect rect; rect.SetRectEmpty();
if (!CWnd::Create(TOOLBARCLASSNAME, NULL, dwStyle,
rect, pParentWnd, nID))
return FALSE;
// Note: Parent must resize itself for control bar to be resized
return TRUE;
}
其中:
Create函數(shù)的參數(shù)1表示工具條的父窗口。參數(shù)2指定窗口風(fēng)格和CBRS_風(fēng)格,缺省值為 WS_CHILD | WS_VISIBLE | CBRS_TOP,其中WS_CHILD和WS_VISIBLE是窗口風(fēng)格,CBRS_TOP是CBRS_風(fēng)格。參數(shù)3指定工具條ID,缺省值為AFX_IDW_TOOLBAR(0X0E800或者59392)。如果還有多個(gè)工具欄要顯示,在創(chuàng)建它們時(shí)則必須給每個(gè)工具欄指明ID。
首先,Create函數(shù)把參數(shù)2(dwStyle)指定的窗口風(fēng)格和CBRS_風(fēng)格分離出來,窗口風(fēng)格保留在dwStyle中,CBRS_風(fēng)格保存到成員變量m_dwStyle中。CToolBar::PreCreateWindow將進(jìn)一步修改這些風(fēng)格。
接著,Create函數(shù)調(diào)用了函數(shù)AfxDeferRegisterClass。它如果沒有注冊(cè)TOOLBARCLASSNAME表示的“窗口類”,就注冊(cè)該類;否則,返回TRUE,表示已經(jīng)注冊(cè)。TOOLBARCLASSNAME表示的字符串是“ToolbarWindow32”,即“窗口類”名稱。
然后,調(diào)用CWnd::Create(7個(gè)參數(shù))使用“ToolbarWindow32”“窗口類”創(chuàng)建工具欄。
Create在創(chuàng)建窗口的過程中,用MFC的標(biāo)準(zhǔn)窗口過程取代原來的窗口過程,如同CFormView和CDialog窗口創(chuàng)建時(shí)窗口過程被取代一樣,并發(fā)送WM_CREATE和WM_NCCREATE消息。
至于添加向工具欄添加按鈕,則由函數(shù)LoadToolBar完成。在分析LoadToolBar函數(shù)之前,先討論OnCreate、OnNcCreate等函數(shù)。
CControlBar提供了消息處理函數(shù)OnCreate來處理WM_CREATE消息。
int CControlBar::OnCreate(LPCREATESTRUCT lpcs)
{
//調(diào)用基類的實(shí)現(xiàn)
if (CWnd::OnCreate(lpcs) == -1)
return -1;
//針對(duì)工具欄,是否有Tooltip特性
if (m_dwStyle & CBRS_TOOLTIPS)
EnableToolTips();
//得到父窗口,并添加自身到其控制條列表中
CFrameWnd *pFrameWnd = (CFrameWnd*)GetParent();
if (pFrameWnd->IsFrameWnd())
{
m_pDockSite = pFrameWnd;
m_pDockSite->AddControlBar(this);
}
return 0;
}
如果需要支持Tooltips,則OnCreate調(diào)用EnableTooltips。
m_pDockSite是CControlBar的和泊位相關(guān)的成員變量,這里把它初始化為擁有工具欄的父邊框窗口,該邊框窗口把控制條加入其控制條列表m_listControlBars中。
在處理WM_CREATE之前,派生類先處理消息WM_NCCREAE。例如,CToolBar覆蓋了OnNcCreate函數(shù)。
CToolBar對(duì)WM_NCCREATE消息的處理如下:
BOOL CToolBar::OnNcCreate(LPCREATESTRUCT lpCreateStruct)
{
if (!CControlBar::OnNcCreate(lpCreateStruct))
return FALSE;
// if the owner was set before the toolbar was created, set it now
if (m_hWndOwner != NULL)
DefWindowProc(TB_SETPARENT, (WPARAM)m_hWndOwner, 0);
DefWindowProc(TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0);
return TRUE;
}
CToolBar覆蓋CcontrolBar的該函數(shù)用來設(shè)置工具條的所屬窗口和描述工具條按鈕結(jié)構(gòu)的大小,這兩個(gè)動(dòng)作都是通過給工具條窗口發(fā)送消息來實(shí)現(xiàn)的。因?yàn)檫@些消息被送給控制窗口類的窗口過程(Windows提供的)來處理,所以直接調(diào)用DefWindowProc,省卻了消息發(fā)送的過程。
在控制窗口創(chuàng)建之后,對(duì)于工具條來說,下一步就是向工具欄添加按鈕。
通過函數(shù)LoadToolBar完成向工具欄添加按鈕的任務(wù),其實(shí)現(xiàn)如下:
BOOL CToolBar::LoadToolBar(LPCTSTR lpszResourceName)
{
ASSERT_VALID(this);
ASSERT(lpszResourceName != NULL);
//查找并確認(rèn)按鈕位圖、字符串等資源的位置
HINSTANCE hInst = AfxFindResourceHandle(lpszResourceName, RT_TOOLBAR);
HRSRC hRsrc = ::FindResource(hInst, lpszResourceName, RT_TOOLBAR);
if (hRsrc == NULL)
return FALSE;
//鎖定資源
HGLOBAL hGlobal = LoadResource(hInst, hRsrc);
if (hGlobal == NULL)
return FALSE;
CToolBarData* pData = (CToolBarData*)LockResource(hGlobal);
if (pData == NULL)
return FALSE;
ASSERT(pData->wVersion == 1);
//復(fù)制與各個(gè)位圖對(duì)應(yīng)的命令I(lǐng)D到數(shù)組pItem
UINT* pItems = new UINT[pData->wItemCount];
for (int i = 0; i < pData->wItemCount; i++)
pItems[i] = pData->items()[i];
//添加按鈕到工具欄,指定各個(gè)按鈕對(duì)應(yīng)的ID
BOOL bResult = SetButtons(pItems, pData->wItemCount);
delete[] pItems;
//設(shè)置按鈕的位圖
if (bResult)
{
// set new sizes of the buttons
CSize sizeimage(pData->wWidth, pData->wHeight);
CSize sizeButton(pData->wWidth + 7, pData->wHeight + 7);
SetSizes(sizeButton, sizeimage);
// load bitmap now that sizes are known by the toolbar control
bResult = LoadBitmap(lpszResourceName);
}
UnlockResource(hGlobal);
FreeResource(hGlobal);
return bResult;
}
LoadToolBar函數(shù)的參數(shù)指定了資源。ToolBar資源的類型是RT_TOOLBAR,ToolBar位圖資源的類型是RT_BITMAP。
在RT_TOOLBAR類型的資源讀入內(nèi)存之后,可以用CToolBarData結(jié)構(gòu)描述。一個(gè)這樣的結(jié)構(gòu)包括了ToolBar資源的如下信息:
工具條位圖的版本,寬度,高度,個(gè)數(shù),各個(gè)位圖對(duì)應(yīng)的命令I(lǐng)D。
然后,LoadToolBar把這些命令I(lǐng)D被復(fù)制到數(shù)組pItem中;根據(jù)位圖寬度、高度形成按鈕尺寸sizeButton和位圖尺寸sizeimage。
接著,調(diào)用SetBottons添加按鈕到工具欄,把各個(gè)按鈕和命令I(lǐng)D對(duì)應(yīng)起來;調(diào)用SetSizes設(shè)置按鈕和位圖的尺寸大??;調(diào)用LoadBitmap添加或者取代工具條的位圖列表。這些動(dòng)作都是調(diào)用工具欄“窗口類”的窗口過程完成的。例如,SetButtons的實(shí)現(xiàn):
BOOL CToolBar::SetButtons(const UINT* lpIDArray, int nIDCount)
{
ASSERT_VALID(this);
ASSERT(nIDCount >= 1); // must be at least one of them
ASSERT(lpIDArray == NULL ||
AfxIsValidAddress(lpIDArray, sizeof(UINT) * nIDCount, FALSE));
//首先,刪除工具條中現(xiàn)有的按鈕
int nCount = (int)DefWindowProc(TB_BUTTONCOUNT, 0, 0);
while (nCount--)
VERIFY(DefWindowProc(TB_DELETEBUTTON, 0, 0));
if (lpIDArray != NULL)//命令I(lǐng)D數(shù)組非空
{
//添加新按鈕
TBBUTTON button; memset(&button, 0, sizeof(TBBUTTON));
int iimage = 0;
for (int i = 0; i < nIDCount; i++)
{
button.fsState = TBSTATE_ENABLED;
if ((button.idCommand = *lpIDArray++) == 0)
{
//按鈕之間分隔
button.fsStyle = TBSTYLE_SEP;
//按鈕之間隔8個(gè)像素
button.iBitmap = 8;
}
else
{
//有位圖和命令I(lǐng)D的按鈕
button.fsStyle = TBSTYLE_BUTTON;
button.iBitmap = iimage++;//設(shè)置位圖索引
}
//添加按鈕
if (!DefWindowProc(TB_ADDBUTTONS, 1, (LPARAM)&button))
return FALSE;
}
}
else//命令I(lǐng)D數(shù)組空,添加空按鈕
{
TBBUTTON button; memset(&button, 0, sizeof(TBBUTTON));
button.fsState = TBSTATE_ENABLED;
for (int i = 0; i < nIDCount; i++)
{
ASSERT(button.fsStyle == TBSTYLE_BUTTON);
if (!DefWindowProc(TB_ADDBUTTONS, 1, (LPARAM)&button))
return FALSE;
}
}
//記錄按鈕個(gè)數(shù)到成員變量m_nCount中
m_nCount = (int)DefWindowProc(TB_BUTTONCOUNT, 0, 0);
//稍后放置按鈕
m_bDelayedButtonLayout = TRUE;
return TRUE;
}
函數(shù)的參數(shù)1是一個(gè)數(shù)組,數(shù)組的各個(gè)元素就是命令I(lǐng)D;參數(shù)2是按鈕的個(gè)數(shù)。首先,SetButtons刪除工具條原來的按鈕;然后,添加新的按鈕,若命令I(lǐng)D數(shù)組非空,則把每一個(gè)按鈕和命令I(lǐng)D對(duì)應(yīng)并分配位圖索引,否則設(shè)置空按鈕并返回FALSE;最后,記錄按鈕個(gè)數(shù)。
從SetButtons的實(shí)現(xiàn)可以看出,對(duì)工具條的所有操作都是通過工具條“窗口類”的窗口過程完成的,SetSizes、LoadBitmap也是如此,這里不作討論。
至此,分析了MFC創(chuàng)建工具條窗口的過程。對(duì)于狀態(tài)欄和對(duì)話框工具欄有類似的步驟,但也有不同之處。
CStatusBar的Create使用“msctls_statusbar32”“窗口類”創(chuàng)建狀態(tài)欄,窗口ID為AFX_IDW_STATUS_BAR(0XE801),然后通過成員函數(shù)SetIndictors給狀態(tài)欄分格,類似于給工具條添加按鈕的過程,它實(shí)際上是通過狀態(tài)欄“窗口類”的窗口過程完成的。
CDialogBar的Create使用CreateDlg創(chuàng)建對(duì)話框工具欄,類似于CFormView的過程。在工具欄窗口創(chuàng)建之后,要添加到父窗口的工具欄列表中,這通過CControlBar::OnCreate完成。這樣創(chuàng)建的結(jié)果導(dǎo)致窗口過程使用MFC的統(tǒng)一的窗口過程,相應(yīng)“窗口類”的窗口過程也將在缺省處理中被調(diào)用,這一點(diǎn)如同CFormView和CDialog中所描述的。在初始化對(duì)話框的時(shí)候完成了各個(gè)控制按鈕的添加。
CStatusBar和CdialogBar都沒有處理消息WM_NCCREATE。
關(guān)于CStautsBar和CDialogBar創(chuàng)建過程的具體實(shí)現(xiàn),這里不作詳細(xì)討論了。
描述了控制條的創(chuàng)建,順便考察其銷毀的設(shè)計(jì)。
工具條、狀態(tài)欄等這些控制窗口都要使用DestroyWindow來銷毀,所有有關(guān)操作集中由CControlBar處理。CControlBar覆蓋了虛擬函數(shù)DestroyWindow、PostNcDestroy和消息處理函數(shù)OnDestroy。
當(dāng)然,各個(gè)派生類的虛擬析構(gòu)函數(shù)被實(shí)現(xiàn)。如果成員變量m_bAutoDelete為TRUE,則動(dòng)態(tài)創(chuàng)建的MFC窗口將自動(dòng)銷毀。
工具條等控制條是作為一個(gè)子窗口在父邊框窗口內(nèi)顯示的。為了處理控制條的布置(Layout),首先需要計(jì)算出控制條的尺寸大小,這個(gè)工作被委派給工具條等控制窗口自己來完成。為此,CControlBar提供了兩個(gè)函數(shù)來達(dá)到這個(gè)目的:CalcFixLayout,CalcDynamicLayout。這兩個(gè)函數(shù)都是虛擬函數(shù)。各個(gè)派生類都覆蓋了這兩個(gè)或者其中一個(gè)函數(shù),用來計(jì)算自身的尺寸大小。這些計(jì)算比較瑣碎,在此不作詳細(xì)討論。其次,在父窗口位置或者大小變化時(shí),控制條的大小和位置要作相應(yīng)的調(diào)整。
下面,描述MFC確定或者更新工具條、狀態(tài)欄等位置的步驟:
(1)邊框窗口在必要的時(shí)候調(diào)用虛擬函數(shù)RecalcLayout來重新放置它的控制條和客戶窗口,例如在創(chuàng)建窗口時(shí)、響應(yīng)消息WM_SIZE時(shí)(見5.3.3.5節(jié))邊框窗口的初始化)。
(2)CFrameWnd::RecalcLayout調(diào)用CWnd的成員函數(shù)RepositionBars完成控制條窗口的重新放置。
(3)CWnd::RepositionBars作如下的處理:
RepositionBars首先給各個(gè)控制子窗口發(fā)送(Send)MFC內(nèi)部使用的消息WM_SIZEPARENT,把窗口客戶區(qū)矩形指針傳遞給它們,給它們一個(gè)機(jī)會(huì)來確認(rèn)自己的尺寸。
然后,各個(gè)控制子窗口用OnSizeParent響應(yīng)WM_SIZEPARENT消息;ControlBar實(shí)現(xiàn)了消息處理函數(shù)OnSizeParent,它調(diào)用CalcDynamicLayout等函數(shù)確定本窗口的大小,并從客戶區(qū)矩形中減去自己的尺寸。
在所有的控制子窗口處理了OnSizeParent消息之后,RepositonBars利用返回的信息調(diào)用函數(shù)CalcWindowRect計(jì)算客戶區(qū)窗口(MDI客戶窗口、View等)的大小。
最后,調(diào)用::EndDeferWindowPos或者::SetWindowPos放置所有的窗口(控制子窗口和客戶窗口)。
在窗口被放置的時(shí)候,發(fā)送消息WM_WINDOWPOSCHANGING和WM_WINDOWPOSCHANGED。MFC的實(shí)現(xiàn)中,控制窗口響應(yīng)了前一個(gè)消息,消息處理函數(shù)是OnWindowPosChanging。CControlBar、CToolBar和CStatusBar等實(shí)現(xiàn)了消息處理函數(shù)OnWindowPosChanging。
上述處理過程所涉及的這些函數(shù)中,RecalcLayout是CFrameWnd定義的虛擬函數(shù);RepostionBars是CWnd的成員函數(shù);CalcaWindowRect是CWnd的虛擬函數(shù);OnSizeParent是CControlBar定義的消息處理函數(shù);OnWindowPosChanging是CToolbar、CStatusBar、CDockBar等CControlBar派生類定義的消息處理函數(shù)。
下面,對(duì)其中兩個(gè)函數(shù)RecalcLayout和RepositionBars作一些分析。
RecalcLayout的實(shí)現(xiàn)如下:
void CFrameWnd::RecalcLayout(BOOL bNotify)
{
//RecalcLayout是否正在被調(diào)用
if (m_bInRecalcLayout)
return;
m_bInRecalcLayout = TRUE;
// clear idle flags for recalc layout if called elsewhere
if (m_nIdleFlags & idleNotify)
bNotify = TRUE;
m_nIdleFlags &= ~(idleLayout|idleNotify);
//與OLE相關(guān)的處理
#ifndef _AFX_NO_OLE_SUPPORT
// call the layout hook -- OLE support uses this hook
if (bNotify && m_pNotifyHook != NULL)
m_pNotifyHook->OnRecalcLayout();
#endif
//是否包含浮動(dòng)(floating)控制條的邊框窗口(CMiniFrameWnd類)
if (GetStyle() & FWS_SNAPTOBARS)
{
//計(jì)算控制條和邊框窗口的位置、尺寸并設(shè)置它們的位置
CRect rect(0, 0, 32767, 32767);
RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposQuery,
&rect, &rect, FALSE);
RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST, reposExtra,
&m_rectBorder, &rect, TRUE);
CalcWindowRect(&rect);
SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(),
SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOZORDER);
}
else
//是普通邊框窗口,則設(shè)置其所有子窗口的位置、尺寸
RepositionBars(0, 0xffff, AFX_IDW_PANE_FIRST,
reposExtra, &m_rectBorder);
//本函數(shù)處理完畢
m_bInRecalcLayout = FALSE;
}
該函數(shù)主要的目的是調(diào)用RepositionBars函數(shù),它分兩種情況來調(diào)用RepositionBars函數(shù)。一種情況是當(dāng)前邊框窗口為浮動(dòng)控制條的包容窗口(微型邊框窗口)時(shí);另一種情況是當(dāng)前邊框窗口為普通邊框窗口時(shí)。
RepositionBars的實(shí)現(xiàn)如下:
void CWnd::RepositionBars(UINT nIDFirst, UINT nIDLast, UINT nIDLeftOver,
UINT nFlags, LPRECT lpRectParam, LPCRECT lpRectClient, BOOL bStretch)
{
ASSERT(nFlags == 0 || nFlags == reposQuery || nFlags == reposExtra);
AFX_SIZEPARENTPARAMS layout;
HWND hWndLeftOver = NULL;
layout.bStretch = bStretch;
layout.sizeTotal.cx = layout.sizeTotal.cy = 0;
if (lpRectClient != NULL)
layout.rect = *lpRectClient; //從參數(shù)6得到客戶區(qū)
else
//參數(shù)lpRectClient空,得到客戶區(qū)域
GetClientRect(&layout.rect);
if (nFlags != reposQuery)
//準(zhǔn)備放置各個(gè)子窗口(layout)
layout.hDWP = ::BeginDeferWindowPos(8); // reasonable guess
else
layout.hDWP = NULL; // not actually doing layout
//按一定順序給各個(gè)控制條發(fā)送父窗口resize的消息;
//各個(gè)控制條窗口收到消息后,從客戶區(qū)中扣除自己使用的區(qū)域;
//并且必要的話每個(gè)控制窗口調(diào)用::DeferWindowPos
//剩下的區(qū)域留給nIDLeftOver子窗口
for (HWND hWndChild = ::GetTopWindow(m_hWnd); hWndChild != NULL;
hWndChild = ::GetNextWindow(hWndChild, GW_HWNDNEXT))
{
UINT nIDC = _AfxGetDlgCtrlID(hWndChild);
CWnd* pWnd = CWnd::FromHandlePermanent(hWndChild);
//如果是指定的nIDLeftOver子窗口,則保存其窗口句柄;
//否則,是控制條窗口,給它們發(fā)送WM_SIZEPARENT消息
if (nIDC == nIDLeftOver)
hWndLeftOver = hWndChild;
else if (nIDC >= nIDFirst && nIDC <= nIDLast && pWnd != NULL)
//如果layout->hDWP非空, OnSizeParent則將執(zhí)行窗口布置的操作
::SendMessage(hWndChild, WM_SIZEPARENT, 0, (LPARAM)&layout);
}
//如果是reposQuery,則得到客戶區(qū)矩形,返回
if (nFlags == reposQuery)
{
ASSERT(lpRectParam != NULL);
if (bStretch)
::CopyRect(lpRectParam, &layout.rect);
else
{
lpRectParam->left = lpRectParam->top = 0;
lpRectParam->right = layout.sizeTotal.cx;
lpRectParam->bottom = layout.sizeTotal.cy;
}
return;
}
//其他情況下(reposDefault、reposExtra),則需要執(zhí)行Layout操作
//處理hWndLeftOver(nIDLeftOver子窗口)
if (nIDLeftOver != 0 && hWndLeftOver != NULL)
{
CWnd* pLeftOver = CWnd::FromHandle(hWndLeftOver);
// allow extra space as specified by lpRectBorder
if (nFlags == reposExtra)
{
ASSERT(lpRectParam != NULL);
layout.rect.left += lpRectParam->left;
layout.rect.top += lpRectParam->top;
layout.rect.right -= lpRectParam->right;
layout.rect.bottom -= lpRectParam->bottom;
}
//基于layout.rect表示的客戶尺寸計(jì)算出窗口尺寸
pLeftOver->CalcWindowRect(&layout.rect);
//導(dǎo)致函數(shù)::DeferWindowPos的調(diào)用
AfxRepositionWindow(&layout, hWndLeftOver, &layout.rect);
}
//給所有的窗口設(shè)置尺寸、位置(size and layout)
if (layout.hDWP == NULL || !::EndDeferWindowPos(layout.hDWP))
TRACE0("Warning: DeferWindowPos failed - low system resources.\n");
}
RepositionBars用來改變客戶窗口中控制條的尺寸大小或者位置,其中:
參數(shù)1和參數(shù)2定義了需要重新放置的子窗口ID的范圍,一般是0到0xFFFF。
參數(shù)3指定了一個(gè)子窗口ID,它擁有客戶窗口剩下的空間,一般是AFX_IDW_PANE_FIRST,表示視的窗口ID。
參數(shù)4指定了操作類型,缺省是CWnd::ReposDefault,表示執(zhí)行窗口放置操作,參數(shù)5不會(huì)用到;若取值CWnd::ReposQuery,則表示嘗試進(jìn)行窗口放置(Layout) ,但最后不執(zhí)行這個(gè)操作,只是把參數(shù)5初始化成客戶區(qū)的尺寸大??;若取值CWnd::ReposExtra,則把參數(shù)5的值加到參數(shù)2表示的子窗口的客戶區(qū)域,并執(zhí)行窗口放置操作。
參數(shù)6表示傳遞給函數(shù)的可用窗口客戶區(qū)的尺寸,如果空則使用窗口客戶區(qū)尺寸。
如果執(zhí)行l(wèi)ayout操作的話,該函數(shù)的核心處理就是:
首先,調(diào)用::BeginDeferWindowPos初始化一個(gè)Windows內(nèi)部的多窗口位置結(jié)構(gòu)(Multiple-window - position structure)hDWP;
然后,讓各個(gè)子窗口逐個(gè)調(diào)用::DeferWindowPos,更新hDWP。在調(diào)用::DeferWindowPos之前,要作一個(gè)確定子窗口大小的工作。這些工作通過給各個(gè)控制子窗口發(fā)送消息WM_SIZEPARENT來完成。
控制子窗口通過函數(shù)OnSizeParent響應(yīng)WM_SIZEPARENT消息,先確定自己的尺寸,然后,如果需要進(jìn)行窗口布置(WM_SIZEPARENT消息參數(shù)lParam包含了一個(gè)非空的HDWP結(jié)構(gòu)(lpLayout->hDWP)),則OnSizeParent將調(diào)用AfxRepositionWindow函數(shù)計(jì)算本控制窗口的位置,結(jié)果保存到hDWP中。
在所有的控制窗口尺寸確定之后,剩下的留給窗口hWndLeftOver(如果存在的話)。確定了hWndLeftOver的大小之后,調(diào)用AfxRepositionWindow函數(shù)計(jì)算其位置,結(jié)果保存到hDWP中。
上面提到的函數(shù)AfxRepositionWindow間接調(diào)用了::DeferWindowPos。
最后,::EndDeferWindowPos,使用hDWP安排所有子窗口的位置和大小。
至于其他函數(shù),如OnSizeparent、OnWindowPosChanging、CalcWindowRect,這里不作進(jìn)一步的分析。
MFC內(nèi)部通過給邊框窗口發(fā)送消息WM_SETMESSAGESTRING、WM_POPMESSAGESTRING的方式在狀態(tài)欄中顯示信息。這兩個(gè)消息在afxpriv.h里頭定義。
WM_SETMESSAGESTRING消息表示在狀態(tài)欄中顯示和某個(gè)ID對(duì)應(yīng)的字符串信息或者指定的字符串信息,消息參數(shù)wParam指定了字符串資源ID,消息參數(shù)lParam指定了字符串指針,兩個(gè)消息參數(shù)只有一個(gè)有用。一般,一個(gè)命令I(lǐng)D對(duì)應(yīng)了一個(gè)字符串ID,對(duì)應(yīng)的字符串是命令I(lǐng)D的說明。
消息WM_POPMESSAGESTRING用來重新設(shè)置狀態(tài)欄。
這兩個(gè)消息對(duì)應(yīng)的消息處理函數(shù)分別是OnSetMessageString和OnPopMessageString,OnSetMessageString和OnPopMessageString分別實(shí)現(xiàn)如下:
LRESULT CFrameWnd::OnSetMessageString(WPARAM wParam, LPARAM lParam)
{
//最近一次被顯示的消息字符串IDS(一個(gè)消息對(duì)應(yīng)的字符串)
UINT nIDLast = m_nIDLastMessage;
m_nFlags &= ~WF_NOPOPMSG;
//得到狀態(tài)欄
CWnd* pMessageBar = GetMessageBar();
if (pMessageBar != NULL)
{
LPCTSTR lpsz = NULL;
CString strMessage;
//設(shè)置狀態(tài)欄文本
if (lParam != 0) //指向一個(gè)字符串
{
ASSERT(wParam == 0); // can't have both an ID and a string
lpsz = (LPCTSTR)lParam; // set an explicit string
}
else if (wParam != 0)//一個(gè)字符串資源IDS
{
//打印預(yù)覽時(shí)映射SC_CLOSE成AFX_IDS_PREVIEW_CLOSE;
if (wParam == AFX_IDS_SCCLOSE && m_lpfnCloseProc != NULL)
wParam = AFX_IDS_PREVIEW_CLOSE;
//得到資源ID所標(biāo)識(shí)的字符串
GetMessageString(wParam, strMessage);
lpsz = strMessage;
}
//在狀態(tài)欄中顯示文本
pMessageBar->SetWindowText(lpsz);
// 根據(jù)最近一次選擇的消息更新狀態(tài)條所屬窗口的有關(guān)記錄
CFrameWnd* pFrameWnd = pMessageBar->GetParentFrame();
if (pFrameWnd != NULL)
{
//記錄最近一次顯示的消息字符串
pFrameWnd->m_nIDLastMessage = (UINT)wParam;
//記錄最近一次Tracking的命令I(lǐng)D和字符串IDS
pFrameWnd->m_nIDTracking = (UINT)wParam;
}
}
m_nIDLastMessage = (UINT)wParam; // new ID (or 0)
m_nIDTracking = (UINT)wParam; // so F1 on toolbar buttons work
return nIDLast;
}
OnSetMessageString函數(shù)直接或者從ID從字符串資源中得到字符串指針。如果是從ID得到字符串指針,則函數(shù)GetMessageString被調(diào)用。
和命令I(lǐng)D對(duì)應(yīng)的字符串由兩部分組成,前一部分用于在狀態(tài)欄顯示,后一部分用于Tooltip顯示,分隔符號(hào)是“\n”。例如,字符串ID_APP_EXIT(對(duì)應(yīng)“退出”菜單、按鈕)是“Exit Application\nExit”,當(dāng)鼠標(biāo)落在“退出”按鈕上時(shí),狀態(tài)欄顯示“Exit Application”,Tooltip顯示“Exit”。根據(jù)這種格式,GetMessageString分離出第一部分的文本信息。至于第二部分的用途將在討論Tooltip的章節(jié)將用到。
得到了字符串之后,OnSetMessageString調(diào)用狀態(tài)欄的SetWindowText函數(shù)。SetWindowText導(dǎo)致消息WM_SETTEXT消息發(fā)送給狀態(tài)欄,狀態(tài)欄的消息處理函數(shù)OnSetText被調(diào)用,實(shí)際上等于調(diào)用了SetPaneText(0, lpsz),即在狀態(tài)欄的第0格中顯示字符串lpsz的信息。對(duì)于工具欄來說,SetWindowText可以認(rèn)為是SetPaneText(0, lpsz)的簡(jiǎn)化版本。
順便指出,pMessageBar->GetParentFrame()返回主邊框窗口,即使pMessageBar指向漂浮的工具條。關(guān)于泊位和漂浮,見后面13.2.5節(jié)的描述。
關(guān)于OnSetText,其實(shí)現(xiàn)如下:
LRESULT CStatusBar::OnSetText(WPARAM, LPARAM lParam)
{
ASSERT_VALID(this);
ASSERT(::IsWindow(m_hWnd));
int nIndex = CommandToIndex(0); //返回0
if (nIndex < 0)
return -1;
return SetPaneText(nIndex, (LPCTSTR)lParam) ? 0 : -1;
}
LRESULT CFrameWnd::OnPopMessageString(WPARAM wParam,
LPARAM lParam)
{
//WF_NOPOPMSG表示邊框窗口不處理WM_POPMESSAGESTRING
if (m_nFlags & WF_NOPOPMSG)
return 0;
//調(diào)用OnSetMessageString
return SendMessage(WM_SETMESSAGESTRING, wParam, lParam);
}
一般,在清除狀態(tài)欄消息時(shí),發(fā)送WM_POPMESSAGESTRING,通過消息參數(shù)wParam指定一個(gè)字符串資源,其ID 為AFX_IDS_IDLEMESSAGE,對(duì)應(yīng)的字符串是“Ready”。
狀態(tài)欄的一個(gè)重要作用是顯示菜單命令或者工具條按鈕的提示信息。本節(jié)討論如何顯示菜單命令的提示信息,關(guān)于工具條按鈕在這方面的討論見后面13.2.4.4章節(jié)。
顯示菜單命令的提示信息,就是每當(dāng)一個(gè)菜單項(xiàng)被選中之后,在狀態(tài)欄顯示該菜單的功能、用法等信息。這些信息以字符串資源的形式保存,字符串ID對(duì)應(yīng)于菜單項(xiàng)的命令I(lǐng)D。
所以,必須處理菜單選擇消息WM_MENUSELECT。CFrameWnd實(shí)現(xiàn)了消息處理函數(shù)OnMenuSelect,其實(shí)現(xiàn)如下:
void CFrameWnd::OnMenuSelect(UINT nItemID,
UINT nFlags, HMENU /*hSysMenu*/)
{
CFrameWnd* pFrameWnd = GetTopLevelFrame();
ASSERT_VALID(pFrameWnd);
//跟蹤被選中的菜單項(xiàng)
if (nFlags == 0xFFFF)
{
//取消菜單操作
m_nFlags &= ~WF_NOPOPMSG;
if (!pFrameWnd->m_bHelpMode)
m_nIDTracking = AFX_IDS_IDLEMESSAGE;
else
m_nIDTracking = AFX_IDS_HELPMODEMESSAGE;
//在狀態(tài)欄顯示
SendMessage(WM_SETMESSAGESTRING, (WPARAM)m_nIDTracking);
ASSERT(m_nIDTracking == m_nIDLastMessage);
// update right away
CWnd* pWnd = GetMessageBar();
if (pWnd != NULL)
pWnd->UpdateWindow();
}
else
{
//選中分隔欄、Popup子菜單或者沒有選中一個(gè)菜單項(xiàng)
if (nItemID == 0 || nFlags & (MF_SEPARATOR|MF_POPUP))
{
// nothing should be displayed
m_nIDTracking = 0;
}
else if (nItemID >= 0xF000 && nItemID < 0xF1F0) // max of 31 SC_s
{
//系統(tǒng)菜單的菜單項(xiàng)被選中
m_nIDTracking = ID_COMMAND_FROM_SC(nItemID);
ASSERT(m_nIDTracking >= AFX_IDS_SCFIRST &&
m_nIDTracking < AFX_IDS_SCFIRST + 31);
}
else if (nItemID >= AFX_IDM_FIRST_MDICHILD)
{
//如果選中的菜單項(xiàng)表示一個(gè)MDI子窗口
m_nIDTracking = AFX_IDS_MDICHILD;
}
else
{
//選中了一個(gè)菜單項(xiàng)
m_nIDTracking = nItemID;
}
pFrameWnd->m_nFlags |= WF_NOPOPMSG;
}
// when running in-place, it is necessary to cause a message to
// be pumped through the queue.
if (m_nIDTracking != m_nIDLastMessage && GetParent() != NULL)
PostMessage(WM_KICKIDLE);
}
OnMenuSelect的作用在于跟蹤當(dāng)前選中的菜單項(xiàng),把菜單項(xiàng)對(duì)應(yīng)的ID保存在CFrameWnd的成員變量m_nIDTracking中。
如果菜單項(xiàng)沒有選中,或者選中的是一個(gè)子菜單,則設(shè)置nIDTracking為0。
如果選中的是系統(tǒng)菜單,則把系統(tǒng)菜單ID轉(zhuǎn)換成一個(gè)對(duì)應(yīng)的命令I(lǐng)D;保存該值到nIDTracking中。
如果選中的菜單是MDI子窗口創(chuàng)建時(shí)添加的(用來表示MDI子窗口),則轉(zhuǎn)換菜單ID為AFX_IDS_MDICHILD,所有對(duì)應(yīng)MDI子窗口的菜單項(xiàng)都使用AFX_IDS_MDICHILD,保存該值到nIDTracking中。
其他情況,就是選中菜單項(xiàng)的ID,把它保存到nIDTracking中。
跟蹤被選擇的菜單項(xiàng)并保存其ID在m_nIDTracking中,OnEnterIdle將用到m_nIDTracking。OnEnterIlde是消息WM_ENTERIDLE的處理函數(shù),CFrameWnd的實(shí)現(xiàn)如下。
void CFrameWnd::OnEnterIdle(UINT nWhy, CWnd* pWho)
{
CWnd::OnEnterIdle(nWhy, pWho);
//若不是因?yàn)椴藛芜x擇進(jìn)入該函數(shù)
//或者當(dāng)前跟蹤到的菜單項(xiàng)ID是最近一次處理的,則返回
if (nWhy != MSGF_MENU || m_nIDTracking == m_nIDLastMessage)
return;
//將發(fā)送消息WM_SETMESSAGETEXT
//在狀態(tài)欄顯示m_nIDTracking對(duì)應(yīng)的字符串
SetMessageText(m_nIDTracking);
ASSERT(m_nIDTracking == m_nIDLastMessage);
}
當(dāng)一個(gè)對(duì)話框或者菜單被顯示的時(shí)候,Windows發(fā)送WM_ENTERIDLE消息。消息參數(shù)wParam取值為MSGF_DIALOGBOX或者M(jìn)SGF_MENU。前者表示顯示對(duì)話框時(shí)發(fā)送該消息,這時(shí)消息參數(shù)lParam表示對(duì)話框的句柄;后者表示顯示菜單時(shí)發(fā)送該消息,這時(shí)消息參數(shù)lParam表示菜單的句柄。
經(jīng)過消息映射,wParam的值傳遞給OnEnterIdle的參數(shù)nWhy,參數(shù)lParam的值傳給參數(shù)who。如果參數(shù)1取值為MSGF_MENU,并且OnEnterIdle最近一次在菜單顯示被調(diào)用時(shí)的菜單ID不同于這一次,則調(diào)用SetMessageText在狀態(tài)欄顯示對(duì)應(yīng)ID命令的字符串,并且記錄當(dāng)前菜單ID到變量m_nIDTracking中(見消息處理函數(shù)OnSetMessageText)。
這樣,在菜單選擇期間,用戶選擇的菜單項(xiàng)ID被OnMenuSelect記錄,在消息WM_ENTERIDLE處理時(shí)在狀態(tài)欄顯示ID命令的提示。
工具條(包括對(duì)話框工具條)是一個(gè)子窗口,它們可以響應(yīng)各種消息。如果按標(biāo)準(zhǔn)的Windows消息和命令消息的分發(fā)途徑,一些消息不能送到擁有工具條的邊框窗口,因?yàn)檫@些消息都將被工具條(對(duì)話框工具條)處理掉。所以,CControlBar覆蓋了虛擬函數(shù)PreTranslateMessage和WindowProc以便實(shí)現(xiàn)特定的消息分發(fā)路徑。
CControlBar 的WindowProc實(shí)現(xiàn)了如下的消息分發(fā)路徑:
用戶對(duì)控制條的輸入消息或者分發(fā)給CControlBar及其派生類處理,或者送給擁有控制條的邊框窗口處理,或者送給Windows控制“窗口類”的窗口過程處理。
WindowProc的實(shí)現(xiàn)如下:
LRESULT CControlBar::WindowProc(UINT nMsg,
WPARAM wParam, LPARAM lParam)
{
ASSERT_VALID(this);
LRESULT lResult;
switch (nMsg)
{
//本函數(shù)處理以下消息
case WM_NOTIFY:
case WM_COMMAND:
case WM_DRAWITEM:
case WM_MEASUREITEM:
case WM_DELETEITEM:
case WM_COMPAREITEM:
case WM_VKEYTOITEM:
case WM_CHARTOITEM:
//首先,工具條處理上述消息,如果沒有處理,則接著給所屬邊框窗口處理
if (OnWndMsg(nMsg, wParam, lParam, &lResult))
return lResult;
else
return GetOwner()->SendMessage(nMsg, wParam, lParam);
}
}
// 最后,給基類CWnd,按缺省方式處理
lResult = CWnd::WindowProc(nMsg, wParam, lParam);
return lResult;
}
從上述實(shí)現(xiàn)可以看出,對(duì)于case范圍內(nèi)的一些消息,如WM_COMMAND、WM_NOTIFY等,控制條如果不能處理,則優(yōu)先分發(fā)給其父窗口(邊框窗口)處理,然后進(jìn)入缺省處理,對(duì)于其他消息直接調(diào)用基類CWnd的實(shí)現(xiàn)(缺省處理)。基于這樣的機(jī)制,可以把用戶對(duì)工具條按鈕或者對(duì)話框工具條內(nèi)控制的操作解釋成相應(yīng)的命令消息,執(zhí)行對(duì)應(yīng)的命令處理。
對(duì)于工具條,當(dāng)用戶選中某個(gè)按鈕時(shí)(鼠標(biāo)左鍵彈起,消息是WM_LBUTTONUP),工具條窗口接收到WM_LBUTTONUP消息,該消息不在CControlBar::WindowProc特別處理的消息范圍內(nèi),于是進(jìn)行缺省處理,也就是說,把該消息派發(fā)給控制條對(duì)應(yīng)的Windows控制的窗口過程處理(即被MFC統(tǒng)一窗口過程所取代的原窗口過程),該窗口過程則把該消息轉(zhuǎn)換成一條命令消息WM_COMMAND,命令I(lǐng)D就是選中按鈕對(duì)應(yīng)的ID,然后,發(fā)送該命令消息給擁有工具條的邊框窗口,導(dǎo)致相應(yīng)的命令處理函數(shù)被調(diào)用。
對(duì)于對(duì)話框工具條,當(dāng)工具條的某個(gè)控制子窗口被選中之后,則產(chǎn)生一條命令通知消息WM_COMMAND,wParam是控制子窗口的ID。CControlBar::WindowProc處理該消息。WindowProc首先調(diào)用OnWndMsg把消息發(fā)送給對(duì)話框工具條或者對(duì)話框工具條的基類處理,如果沒有被處理的話,則OnWndMsg返回FALSE。接著,WindowPoc把命令消息傳遞給父窗口(邊框窗口)處理。由于工具條的控制窗口的ID對(duì)應(yīng)的是命令I(lǐng)D,所以,這條WM_COMMAND消息傳遞給邊框窗口時(shí),被解釋成一個(gè)命令消息,相應(yīng)的命令處理函數(shù)被調(diào)用。
CControlBar覆蓋PreTranslateMessage函數(shù),主要是為了在光標(biāo)落在工具條按鈕上時(shí)顯示FLYBY信息,并且讓對(duì)話框工具條過濾Dialog消息。
BOOL CControlBar::PreTranslateMessage(MSG* pMsg)
{
ASSERT_VALID(this);
ASSERT(m_hWnd != NULL);
//過濾Tooltip消息
if (CWnd::PreTranslateMessage(pMsg))
return TRUE; //是Tooltip消息,已經(jīng)被處理
UINT message = pMsg->message;
//控制條的父窗口,對(duì)工具條和對(duì)話框工具條,總是創(chuàng)建它的邊框窗口
CWnd* pOwner = GetOwner();
//必要的話,在狀態(tài)條顯示工具欄按鈕的提示
if (((m_dwStyle & CBRS_FLYBY) ||
message == WM_LBUTTONDOWN || message == WM_LBUTTONUP) &&
((message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) ||
(message >= WM_NCMOUSEFIRST &&
message <= WM_NCMOUSELAST)))
{
_AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
//確認(rèn)鼠標(biāo)在工具欄的哪個(gè)按鈕上
CPoint point = pMsg->pt;
ScreenToClient(&point);
TOOLINFO ti; memset(&ti, 0, sizeof(TOOLINFO));
ti.cbSize = sizeof(TOOLINFO);
int nHit = OnToolHitTest(point, &ti);
if (ti.lpszText != LPSTR_TEXTCALLBACK)
free(ti.lpszText);
BOOL bNotButton =
message == WM_LBUTTONDOWN && (ti.uFlags & TTF_NOTBUTTON);
if (message != WM_LBUTTONDOWN && GetKeyState(VK_LBUTTON) < 0)
nHit = pThreadState->m_nLastStatus;
//更新狀態(tài)欄的提示信息
if (nHit < 0 || bNotButton)
{
if (GetKeyState(VK_LBUTTON) >= 0 || bNotButton)
{
SetStatusText(-1);
KillTimer(ID_TIMER_CHECK);
}
}
else
{
if (message == WM_LBUTTONUP)
{
SetStatusText(-1);
ResetTimer(ID_TIMER_CHECK, 200);
}
else
{
if ((m_nStateFlags & statusSet) || GetKeyState(VK_LBUTTON) < 0)
SetStatusText(nHit);
else if (nHit != pThreadState->m_nLastStatus)
ResetTimer(ID_TIMER_WAIT, 300);
}
}
pThreadState->m_nLastStatus = nHit;
}
// don't translate dialog messages when in Shift+F1 help mode
CFrameWnd* pFrameWnd = GetTopLevelFrame();
if (pFrameWnd != NULL && pFrameWnd->m_bHelpMode)
return FALSE;
//在IsDialogMessage之前調(diào)用邊框窗口的PreTranslateMessage,
//給邊框窗口一個(gè)處理快捷鍵的機(jī)會(huì)
while (pOwner != NULL)
{
// allow owner & frames to translate before IsDialogMessage does
if (pOwner->PreTranslateMessage(pMsg))
return TRUE;
// try parent frames until there are no parent frames
pOwner = pOwner->GetParentFrame();
}
//過濾給對(duì)話框的消息和來自子窗口的消息
return PreTranslateInput(pMsg);
}
函數(shù)PreTranslateMessage主要是針對(duì)工具欄的,用來處理工具欄的CBRS_FLYBY特征。
對(duì)于對(duì)話框工具欄,也可以有CBRS_FLYBY特征。但在這種情況下,還需要把一些用戶鍵盤輸入解釋成對(duì)話框消息。為了防止快捷鍵被解釋成對(duì)話框消息,在調(diào)用函數(shù)PreTranslateInput之前,必須調(diào)用所有父邊框窗口的PreTranslateMessage,給父邊框窗口一個(gè)機(jī)會(huì)處理用戶的輸入消息,判斷快捷鍵是否被按下。
關(guān)于Tooltip和本PreTranslateMessage函數(shù)處理Tooltip的詳細(xì)解釋見下一節(jié)的討論。
工具條(或?qū)υ捒蚬ぞ邨l)如果指定了CBRS_TOOLTIPS風(fēng)格(創(chuàng)建時(shí)指定或者創(chuàng)建后用SetBarStyle設(shè)置),則當(dāng)鼠標(biāo)落在某個(gè)按鈕上(或者對(duì)話框的子控制窗口)時(shí),在鼠標(biāo)附近彈出一個(gè)文本框──Tooltip提示窗口。
如果還指定了CBRS_FLYBY風(fēng)格,則還在狀態(tài)欄顯示和按鈕(或子控制窗口)ID對(duì)應(yīng)的字符串信息。當(dāng)然,鼠標(biāo)左鍵在某個(gè)按鈕(或子控制窗口)按下時(shí),也要在狀態(tài)欄顯示按鈕的提示信息,當(dāng)左鍵彈起時(shí),則重置狀態(tài)欄的狀態(tài)。
如前所述,Tooltip窗口是Windows控制窗口。MFC使用了CToolTipCtrl類封裝Tooltip的HWND窗口。在一個(gè)線程的生存期間,至多擁有一個(gè)Tooltip窗口,該窗口對(duì)象的指針保存在線程狀態(tài)的成員變量m_pToolTip中。線程狀態(tài)類AFX_THREAD_STATE的析構(gòu)函數(shù)如果檢測(cè)到m_pToolTip,則銷毀MFC窗口對(duì)象和相應(yīng)的Windows窗口對(duì)象。
為了支持Tooltip顯示,CWnd提供了以下函數(shù):EnableTooltip,CancelTooltip,PreTranslateMessage,F(xiàn)ilterTooltipMessage,OnToolHitTest。
EnableTooltip設(shè)置CBRS_TOOLTIP風(fēng)格,相反CancelTootip取消這種風(fēng)格。
PreTranslateMessage調(diào)用了FilterTooltipMessage過濾Tooltip消息。
OnToolHitTest是一個(gè)由CWnd定義的虛擬函數(shù)。CToolBar通過覆蓋該函數(shù),來檢測(cè)對(duì)話框工具欄的控制子窗口或者工具欄按鈕是否被選中、哪個(gè)被選中。
CWnd的PreTranslateMessage在4.5節(jié)討論過,它的實(shí)現(xiàn)如下:
BOOL CWnd::PreTranslateMessage(MSG* pMsg)
{
//處理Tooltip消息
AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();
if (pModuleState->m_pfnFilterToolTipMessage != NULL)
//導(dǎo)致調(diào)用FilterTooltipMessage
(*pModuleState->m_pfnFilterToolTipMessage)(pMsg, this);
//不是Tooltip消息
return FALSE;
}
至于為什么MFC在模塊狀態(tài)中保存一個(gè)處理Tooltip消息的函數(shù)地址,通過該函數(shù)調(diào)用FilterTooltipMessage,是因?yàn)門ooltip窗口是模塊線程局部有效的。
FilterTooltipMessage檢測(cè)是否是Tooltip消息。如果是,在必要時(shí)創(chuàng)建一個(gè)CTooltipCtrl對(duì)象和對(duì)應(yīng)的HWND,調(diào)用OnToolHitTest確定被選中的按鈕或者控制的ID,接著彈出Tooltip窗口。
其他函數(shù)和CTooltipCtrl這里不作詳細(xì)論述了。
Tooltip窗口在彈出之前,它給工具條(或者對(duì)話框工具欄)的父窗口發(fā)送通知消息TTN_NEEDTEXT,請(qǐng)求得到要顯示的文本。
CFrameWnd類處理了TTN_NEEDTEXT通知消息,消息處理函數(shù)是OnToolTipText。
消息映射的定義:
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
這里,使用了擴(kuò)展消息映射宏把子窗口ID在0和0xFFFF之間的控制條窗口的通知消息TTN_NEEDTEXTA和TTN_NEEDTEXTW映射到函數(shù)OnToolTipText。
消息映射的實(shí)現(xiàn):
BOOL CFrameWnd::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)
{
ASSERT(pNMHDR->code == TTN_NEEDTEXTA ||
pNMHDR->code == TTN_NEEDTEXTW);
//讓上一層的邊框窗口優(yōu)先處理該消息
if (GetRoutingFrame() != NULL)
return FALSE;
//分ANSI and UNICODE兩個(gè)處理版本
TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
TCHAR szFullText[256];
CString strTipText;
UINT nID = pNMHDR->idFrom;
//如果idFrom是一個(gè)子窗口,則得到其ID。
if (pNMHDR->code == TTN_NEEDTEXTA &&
(pTTTA->uFlags & TTF_IDISHWND) ||
pNMHDR->code == TTN_NEEDTEXTW &&
(pTTTW->uFlags & TTF_IDISHWND))
{
//idFrom是工具條的句柄
nID = _AfxGetDlgCtrlID((HWND)nID);
}
if (nID != 0) //若是0,為一分隔欄,不是按鈕
{
//得到nID對(duì)應(yīng)的字符串
AfxLoadString(nID, szFullText);
//從上面得到的字符串中取出Tooltip使用的文本
AfxExtractSubString(strTipText, szFullText, 1, '\n');
}
//復(fù)制分離出的文本
#ifndef _UNICODE
if (pNMHDR->code == TTN_NEEDTEXTA)
lstrcpyn(pTTTA->szText, strTipText, _countof(pTTTA->szText));
else
_mbstowcsz(pTTTW->szText, strTipText, _countof(pTTTW->szText));
#else
if (pNMHDR->code == TTN_NEEDTEXTA)
_wcstombsz(pTTTA->szText, strTipText, _countof(pTTTA->szText));
else
lstrcpyn(pTTTW->szText, strTipText, _countof(pTTTW->szText));
#endif
*pResult = 0;
//顯示Tooltip窗口
::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0,
SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE);
return TRUE; //消息處理完畢
}
OnToolTipText是一個(gè)擴(kuò)展映射宏定義的消息處理函數(shù),所以有一個(gè)UINT參數(shù)并且返回BOOL類型的值。不過,由于第二個(gè)參數(shù)(NMHDR)的idFrom域包含了有關(guān)信息,所以第一個(gè)UINT類型的參數(shù)沒有用上。
OnToolTipText也是一個(gè)處理通知消息的例子。其中,通知參數(shù)wParam的結(jié)構(gòu)如4.4.4.2節(jié)所述,具體如下:
typedef struct {
NMHDR hdr; //WM_NOTIFY消息要求的頭
LPTSTR lpszText; //接收工具條按鈕對(duì)應(yīng)文本的緩沖區(qū)
WCHAR szText[80]; //接收Tooltip顯示文本的緩沖區(qū)
HINSTANCE hinst; //包含了szText的實(shí)例句柄
UINT uflags; //標(biāo)識(shí)了NMHDR的idFrom成員的意義
} TOOLTIPTEXT, FAR *LPTOOLTIPTEXT;
uflags如果等于TTF_IDISHWND,則表示通知消息來自對(duì)話框工具條的一個(gè)子窗口,而不是包含工具條按鈕。
OnToolTipText根據(jù)子窗口ID或者工具條按鈕對(duì)應(yīng)的ID,得到字符串ID。如前所述,字符串ID由兩部分組成,第二部分用于Tooltip顯示,分隔符號(hào)是“\n”。根據(jù)這種格式OnToolTipText分離出Tooltip文本。
得到了Tooltip文本之后,可以有三種方法返回文本信息:把文本信息復(fù)制到szText緩沖區(qū);把文本地址復(fù)制到lpszText;復(fù)制字符串資源的ID到lpszText、復(fù)制包含資源的實(shí)例句柄到hint。本函數(shù)采用了第一種方法。
在得到了返回的Tooltip文本之后,該文本在Tooltip窗口中被顯示出來。
其他的OnToolHist等函數(shù)的實(shí)現(xiàn)不作詳細(xì)的解釋了。下面,討論CBRS_FLYBY風(fēng)格的實(shí)現(xiàn)。
CBRS_FLYBY是MFC提供的特征。當(dāng)鼠標(biāo)落在工具條按鈕(或者對(duì)話框工具條的子窗口)上且穩(wěn)定300ms后,在狀態(tài)欄顯示對(duì)應(yīng)的提示信息。如果選中某個(gè)按鈕或者子窗口(鼠標(biāo)左鍵按下),則在相應(yīng)命令消息處理之前在狀態(tài)欄顯示有關(guān)提示信息,之后(鼠標(biāo)左鍵彈起),重新設(shè)置狀態(tài)欄的狀態(tài)信息。
為了支持這種特征,CControlBar覆蓋虛擬函數(shù)PreTranslateMessage來處理和CBRS_FLYBY相關(guān)的消息,該函數(shù)前面已經(jīng)討論過,這里解釋它如何處理CBRS_FLYBY特征。
如果同時(shí)具備
條件1:控制條具有CBRS_FLYBY特征或者當(dāng)前消息是WM_LBUTTONUP或者WM_LBUTTONDOWN。
條件2:當(dāng)前消息是鼠標(biāo)消息(在WM_MOUSEFIRST和WM_MOUSELAST之間或者在WM_NCMOUSEFIRST和WM_NCMOUSELAST之間)。
則進(jìn)行FLYBY處理。
首先,調(diào)用OnToolHitTest測(cè)試用戶是否選中了工具條的按鈕或者子窗口;
如果沒有按鈕或者子窗口被選中,則重新設(shè)置狀態(tài)欄的狀態(tài),取消曾經(jīng)設(shè)置的Check定時(shí)器。重置狀態(tài)欄的狀態(tài)時(shí)調(diào)用了SetStatusText(int nHit)函數(shù),它是CControlBar內(nèi)部使用的函數(shù),若nHit等于-1,它向父窗口發(fā)送WM_POPMESSAGETEXT,消息參數(shù)是AFX_IDS_IDLEMESSAGE,結(jié)果導(dǎo)致狀態(tài)欄顯示“Ready”字樣;否則發(fā)送WM_SETMESSAGETEXT消息,wParm設(shè)置為nHit,結(jié)果導(dǎo)致在狀態(tài)欄顯示ID為nHit的字符串。
如果有按鈕或者子窗口被選中,若左鍵彈起,則重新設(shè)置狀態(tài)欄信息,取消Wait定時(shí)器,并重新設(shè)置Check定時(shí)器,定時(shí)是200ms;若左鍵按下,則在狀態(tài)欄顯示消息ID對(duì)應(yīng)的提示信息;若是其他鼠標(biāo)消息,如果當(dāng)前鼠標(biāo)所在按鈕(子窗口)不同于最近一次,則取消Check定時(shí)器,重新設(shè)置Wait定時(shí)器,定時(shí)300毫秒。
CControlBar覆蓋了消息處理函數(shù)OnTimer,在指定時(shí)間之后,檢查鼠標(biāo)位置,如果鼠標(biāo)還在某個(gè)按鈕或者子窗口上,則在狀態(tài)條顯示提示信息。Wait定時(shí)器在等待之后準(zhǔn)備在狀態(tài)條顯示信息,觸發(fā)一次后被取消;Check定時(shí)器在等待之后,判斷是否需要取消狀態(tài)條當(dāng)前顯示的信息,重新設(shè)置狀態(tài)條,若這樣的話,同時(shí)也取消Check定時(shí)器。
注意,這些鼠標(biāo)消息被處理之后,并沒有終止,它們將繼續(xù)被發(fā)送給控制條的窗口過程處理。
至此,CBRS_FLYBY特征的支持實(shí)現(xiàn)描述完畢。
在MFC下,工具條、狀態(tài)條還有一個(gè)重要的特征,就是自動(dòng)地根據(jù)條件禁止或者允許使用某個(gè)按鈕、窗格等。在4.4.5節(jié)命令消息的處理中,曾詳細(xì)討論了其實(shí)現(xiàn)原理,現(xiàn)在,詳細(xì)地分析所涉及函數(shù)是如何實(shí)現(xiàn)的。有關(guān)的消息處理函數(shù)和虛擬函數(shù)如下。
處理WM_INITIALUPDATE消息的OnInitialUpdate;
處理WM_IDLEUPDATECMDUI消息的OnIdleUpdateCmdUI;
虛擬函數(shù)OnUpdateCmdUI。
回顧5.3.3.5節(jié),在邊框窗口的創(chuàng)建之后,給所有的子窗口發(fā)送初始化消息,控制子窗口用OnInitialUpdate響應(yīng)它,調(diào)用OnIdleUpdateCmdUI完成狀態(tài)的初始化。
OnIdleUpdateCmdUI還在IDLE處理時(shí)進(jìn)行狀態(tài)的更新處理,它生成用于處理狀態(tài)更新消息的命令目標(biāo)pTarget,然后調(diào)用虛擬函數(shù)OnUpdateCmdUI(pTarget,…)來更新工具欄或者狀態(tài)欄的狀態(tài)。
CControlBar的子類都實(shí)現(xiàn)了自己的OnUpdateCmdUI函數(shù),用該函數(shù)生成適當(dāng)?shù)腃CmdUI對(duì)象state,然后調(diào)用CCmdUI的DoUpdate(pTarget,…)給pTarget所指對(duì)象發(fā)送狀態(tài)更新消息。為了完成具體的狀態(tài)更新,從CCmdUI派生出CToolCmdUI和CStatusCCmUI,它們實(shí)現(xiàn)了自己的Enable、SetCheck等等。
CControlBar使用OnInitialUpdate消息處理函數(shù)初始化控制窗口的狀態(tài)。
void CControlBar::OnInitialUpdate()
{
//在窗口顯示之前,更新狀態(tài)
OnIdleUpdateCmdUI(TRUE, 0L);
}
CControlBar實(shí)現(xiàn)了OnInitialUpdate函數(shù),通過它來處理WM_INITIALUPDATE消息。各個(gè)子類不必覆蓋該消息處理函數(shù)。
CControlBar使用OnIdleUpdateCmdUI消息處理函數(shù)處理IDLE消息。
LRESULT CControlBar::OnIdleUpdateCmdUI(WPARAM wParam, LPARAM)
{
// handle delay hide/show
BOOL bVis = GetStyle() & WS_VISIBLE;
UINT swpFlags = 0;
if ((m_nStateFlags & delayHide) && bVis)
swpFlags = SWP_HIDEWINDOW;
else if ((m_nStateFlags & delayShow) && !bVis)
swpFlags = SWP_SHOWWINDOW;
m_nStateFlags &= ~(delayShow|delayHide);
if (swpFlags != 0)
{
SetWindowPos(NULL, 0, 0, 0, 0, swpFlags|
SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
}
// the style must be visible and if it is docked
// the dockbar style must also be visible
if ((GetStyle() & WS_VISIBLE) &&
(m_pDockBar == NULL || (m_pDockBar->GetStyle() & WS_VISIBLE)))
{
//得到父邊框窗口,狀態(tài)更新消息將發(fā)送給它
CFrameWnd* pTarget = (CFrameWnd*)GetOwner();
if (pTarget == NULL || !pTarget->IsFrameWnd())
pTarget = GetParentFrame();
if (pTarget != NULL)
OnUpdateCmdUI(pTarget, (BOOL)wParam);
}
return 0L;
}
OnIdleUpdateCmdUI或者在初始化時(shí)被OnInitialUpdate調(diào)用,或者作為消息處理函數(shù)來處理WM_IDLEUPDATECMDUI消息。
CControlBar實(shí)現(xiàn)了OnIdleUpdateCmdUI函數(shù),把具體的用戶界面更新動(dòng)作委托給虛擬函數(shù)OnUpdateCmdUI完成。
由于各個(gè)用戶界面的特殊性,所以CControlBar本身沒有實(shí)現(xiàn)OnUpdateCmdUI,而是留給各個(gè)派生類去實(shí)現(xiàn)。例如,CToolBar覆蓋了OnUpdateCmdUI,其實(shí)現(xiàn)如下:
void CToolBar::OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler)
{
//定義一個(gè)CCmdUI對(duì)象,CToolCmdUI派生于CCmdUI
CToolCmdUI state;
//給CCmdUI的各個(gè)成員賦值
state.m_pOther = this;
//得到總的按鈕數(shù)目
state.m_nIndexMax = (UINT)DefWindowProc(TB_BUTTONCOUNT, 0, 0);
//逐個(gè)按鈕進(jìn)行狀態(tài)更新
for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax; state.m_nIndex++)
{
//獲取按鈕狀態(tài)信息
TBBUTTON button;
_GetButton(state.m_nIndex, &button);
//得到按鈕的ID
state.m_nID = button.idCommand;
// ignore separators
if (!(button.fsStyle & TBSTYLE_SEP))
{
//優(yōu)先讓CToolBar對(duì)象處理狀態(tài)更新消息
if (CWnd::OnCmdMsg(state.m_nID,
CN_UPDATE_COMMAND_UI, &state, NULL))
continue;//處理了更新消息,更新下一個(gè)按鈕
//CToolBar沒有處理,將發(fā)送給pTarget處理狀態(tài)更新消息
//第二個(gè)參數(shù)bDisableIfNoHndler往下傳
state.DoUpdate(pTarget, bDisableIfNoHndler);
}
}
//更新加到控制條中的對(duì)話框控制的狀態(tài)
UpdateDialogControls(pTarget, bDisableIfNoHndler);
}
CToolBar的OnUpdateCmdUI函數(shù)完成工具條按鈕的狀態(tài)更新。它接受兩個(gè)參數(shù),參數(shù)1表示接收狀態(tài)更新命令消息的對(duì)象,由CControlBar的函數(shù)OnIdleUpdateCmdUI傳遞過來,一般是邊框窗口對(duì)象;參數(shù)2表示如果某條命令消息沒有處理函數(shù)時(shí),對(duì)應(yīng)的用戶接口對(duì)象是否被禁止。
OnUpdateCmdUI通過發(fā)送狀態(tài)更新通知消息,逐個(gè)更新按鈕的狀態(tài)。更新消息首先讓工具條對(duì)象處理,如果沒有處理的話,送給邊框窗口對(duì)象處理,導(dǎo)致狀態(tài)更新命令消息的處理函數(shù)被調(diào)用,參見4.4.5節(jié)。
CStatusBar的OnUpdateCmdUI類似于此。
CDialogBar的OnUpdateCmdUI則調(diào)用了虛擬函數(shù)UpdateDialogControls來進(jìn)行狀態(tài)更新,CWnd提供了該函數(shù)的實(shí)現(xiàn),過程類似于CToolBar的函數(shù)OnUpdateCmdUI。
那么,菜單項(xiàng)的自動(dòng)更新如何實(shí)現(xiàn)的呢?OnInitMenuPopup在菜單項(xiàng)狀態(tài)的自動(dòng)更新中曾經(jīng)被提到,其實(shí)現(xiàn)如下:
void CFrameWnd::OnInitMenuPopup(CMenu* pMenu, UINT, BOOL bSysMenu)
{
AfxCancelModes(m_hWnd);
if (bSysMenu)
return; // don't support system menu
ASSERT(pMenu != NULL);
// check the enabled state of various menu items
CCmdUI state;
state.m_pMenu = pMenu;
ASSERT(state.m_pOther == NULL);
ASSERT(state.m_pParentMenu == NULL);
//判斷菜單是否在頂層菜單(top level menu)中彈出,如果這樣
//則設(shè)置m_pParentMenu指向頂層菜單,否則m_pParentMenu
//為空,表示它是一個(gè)二級(jí)彈出菜單
HMENU hParentMenu;
//是否是浮動(dòng)式的彈出菜單(floating pop up menu)
if (AfxGetThreadState()->m_hTrackingMenu == pMenu->m_hMenu)
state.m_pParentMenu = pMenu; // parent == child for tracking popup
else if ((hParentMenu = ::GetMenu(m_hWnd)) != NULL)//
{
CWnd* pParent = GetTopLevelParent();
// child windows don't have menus -- need to go to the top!
//得到頂層窗口的菜單
if (pParent != NULL &&
(hParentMenu = ::GetMenu(pParent->m_hWnd)) != NULL)
{
int nIndexMax = ::GetMenuItemCount(hParentMenu);
//確定頂層窗口的菜單是否包含本菜單項(xiàng)
for (int nIndex = 0; nIndex < nIndexMax; nIndex++)
{
if (::GetSubMenu(hParentMenu, nIndex) == pMenu->m_hMenu)
{
//頂層窗口菜單是本菜單的父菜單
state.m_pParentMenu = CMenu::FromHandle(hParentMenu);
break;
}
}
}
}
//本菜單的菜單項(xiàng)(menu item)數(shù)量
state.m_nIndexMax = pMenu->GetMenuItemCount();
//對(duì)所有菜單項(xiàng)逐個(gè)進(jìn)行狀態(tài)更新
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 == 0)
continue; // menu separator or invalid cmd - ignore it
ASSERT(state.m_pOther == NULL);
ASSERT(state.m_pMenu != NULL);
if (state.m_nID == (UINT)-1)
{
// 可能是一個(gè)popup菜單,得到其第一個(gè)子菜單項(xiàng)目
state.m_pSubMenu = pMenu->GetSubMenu(state.m_nIndex);
if (state.m_pSubMenu == NULL ||
(state.m_nID = state.m_pSubMenu->GetMenuItemID(0)) == 0 ||
state.m_nID == (UINT)-1)
{
continue; // 找不到popup菜單的子菜單項(xiàng)
}
//popup菜單不會(huì)被自動(dòng)的禁止
state.DoUpdate(this, FALSE);
}
else
{
//正常的菜單項(xiàng),若邊框窗口的m_bAutoMenuEnable設(shè)置為
//TURE且菜單項(xiàng)非系統(tǒng)菜單,則自動(dòng)enable/disable該菜單項(xiàng)
state.m_pSubMenu = NULL;
state.DoUpdate(this, m_bAutoMenuEnable && state.m_nID < 0xF000);
}
//經(jīng)過菜單狀態(tài)的更新處理,可能增加或刪除了一些菜單項(xiàng)
UINT nCount = pMenu->GetMenuItemCount();
if (nCount < state.m_nIndexMax)
{
state.m_nIndex -= (state.m_nIndexMax - nCount);
while (state.m_nIndex < nCount &&
pMenu->GetMenuItemID(state.m_nIndex) == state.m_nID)
{
state.m_nIndex++;
}
}
state.m_nIndexMax = nCount;
}
}
菜單彈出之前,發(fā)送WM_INITMENUPOPUP消息,OnInitMenuPopup消息處理函數(shù)被調(diào)用,逐個(gè)更新菜單項(xiàng)目(menu item)的狀態(tài)。程序員可以處理它們對(duì)應(yīng)的狀態(tài)更新消息,禁止/允許菜單項(xiàng)目被使用(disable/enable),在菜單項(xiàng)目上打鉤或者取消(checked/unchecked),等等。
這里討論顯示或者隱藏工具欄、狀態(tài)欄的操作,以及工具欄、狀態(tài)欄被顯示/隱藏時(shí),相關(guān)的兩個(gè)菜單項(xiàng)ID_VIEW_STATUS_BAR、ID_VIEW_TOOLBAR的狀態(tài)更新。這兩個(gè)菜單命令及對(duì)應(yīng)的狀態(tài)更新命令是標(biāo)準(zhǔn)命令消息所包含的。MFC邊框窗口實(shí)現(xiàn)了菜單命令消息的處理和菜單項(xiàng)狀態(tài)的更新。
CFrameWnd提供了OnBarCheck來響應(yīng)與ID_VIEW_STATUS_BAR、ID_VIEW_TOOLBAR菜單項(xiàng)對(duì)應(yīng)的命令。
消息映射:
ON_COMMAND_EX(ID_VIEW_STATUS_BAR, OnBarCheck)
ON_COMMAND_EX(ID_VIEW_TOOLBAR, OnBarCheck)
這里,使用了擴(kuò)展命令消息映射宏把ID_VIEW_STATUS_BAR和ID_VIEW_TOOLBAR命令映射給同一個(gè)函數(shù)OnBarCheck處理。
OnBarCheck函數(shù)的實(shí)現(xiàn):
BOOL CFrameWnd::OnBarCheck(UINT nID)
{
ASSERT(ID_VIEW_STATUS_BAR == AFX_IDW_STATUS_BAR);
ASSERT(ID_VIEW_TOOLBAR == AFX_IDW_TOOLBAR);
//得到工具條或者狀態(tài)條
CControlBar* pBar = GetControlBar(nID);
if (pBar != NULL)
{
//若控制條可見,則隱藏它;否則,顯示它
ShowControlBar(pBar, (pBar->GetStyle() & WS_VISIBLE) == 0, FALSE);
//處理完畢
return TRUE;
}
//可以讓下一個(gè)命令目標(biāo)繼續(xù)處理
return FALSE;
}
由于是擴(kuò)展映射宏定義的消息處理函數(shù),所以O(shè)nBarCheck函數(shù)有一個(gè)UINT類型的參數(shù)和一個(gè)BOOL返回值。
當(dāng)用戶從“View”菜單選擇打了鉤的“Toolbar”時(shí),消息處理函數(shù)OnBarCheck被調(diào)用,參數(shù)就是菜單項(xiàng)的ID號(hào)ID_VIEW_TOOLBAR,它等于工具條的子窗口IDAFX_IDW_TOOLBAR。處理結(jié)果,工具條被隱藏;當(dāng)再次選擇該菜單項(xiàng)則工具條被顯示。
處理狀態(tài)條的過程類似于工具條的處理。
ShowControlBar是CFrameWnd的成員函數(shù),參數(shù)1表示控制條對(duì)象指針,參數(shù)2表示顯示(TRUE)或者隱藏(FALSE),參數(shù)3表示是立即顯示(FALSE)或者延遲顯示(TRUE)。
如果工具條或者狀態(tài)條被隱藏,則相應(yīng)的菜單項(xiàng)ID_VIEW_STATUS_BAR 或者ID_VIEW_TOOLBAR 變成uncheked(菜單項(xiàng)被標(biāo)記為沒有選擇),否則,checked(菜單項(xiàng)被標(biāo)記選擇)。CFrameWnd實(shí)現(xiàn)了這兩個(gè)菜單項(xiàng)的狀態(tài)更新處理,列舉其中一個(gè)如下:
聲明處理ID_VIEW_TOOLBAR的狀態(tài)更新消息:
ON_UPDATE_COMMAND_UI(ID_VIEW_TOOLBAR, OnUpdateControlBarMenu)
函數(shù)的實(shí)現(xiàn):
void CFrameWnd::OnUpdateControlBarMenu(CCmdUI* pCmdUI)
{
ASSERT(ID_VIEW_STATUS_BAR ==
AFX_IDW_STATUS_BAR);
ASSERT(ID_VIEW_TOOLBAR == AFX_IDW_TOOLBAR);
CControlBar* pBar = GetControlBar(pCmdUI->m_nID);
//存在工具欄
if (pBar != NULL)
{
//工具條窗口被顯示則checked,被隱藏則uncheked
pCmdUI->SetCheck((pBar->GetStyle() & WS_VISIBLE) != 0);
return;
}
pCmdUI->ContinueRouting();
}
GetControlBar是CFrameWnd的成員函數(shù),用來返回邊框窗口的指定ID的控制條對(duì)象(指定ID是控制條的子窗口ID)。
工具條可以泊位在邊框窗口的任一邊(上、下、左、右),或者漂浮在屏幕上的任何地方。
首先,邊框窗口調(diào)用CFrameWnd::EnableDocking函數(shù)使控制條泊位在邊框窗口中有效,指明在邊框窗口的哪邊接受泊位。如果想在任何邊都可以泊位,則使用參數(shù)CBRS_ALIGN_ANY。
然后,工具條調(diào)用ControlBar::EnableDocking使泊位對(duì)工具條有效,如果在調(diào)用ControlBar::EnableDocking時(shí)指定的泊位目的邊和邊框窗口能夠泊位的邊不符合,那么工具條不能泊位,它將漂浮。
最后,邊框窗口調(diào)用CFrameWnd::DockControlBar泊位工具條。
邊框窗口、泊位條、工具條的包含關(guān)系如下:
邊框窗口
泊位條1
工具條1
工具條2
…
泊位條2
…
邊框窗口包含1到4個(gè)泊位條子窗口,每個(gè)泊位條包含若干個(gè)控制條子窗口。
CFrameWnd::EnableDocking指定哪邊接受泊位,則為泊位準(zhǔn)備一個(gè)泊位條。泊位條用CDockBar描述,派生于CControlBar。如果指定任何邊都可以泊位,則創(chuàng)建四個(gè)CDockBar對(duì)象和對(duì)應(yīng)的HWND窗口。然后,調(diào)用ControlBar::EnableDocking在對(duì)應(yīng)的泊位條內(nèi)安置工具條。
MFC設(shè)計(jì)了CDockBar類和CFrameWnd的一些函數(shù)來實(shí)現(xiàn)泊位,具體代碼實(shí)現(xiàn)在此不作詳細(xì)討論。
邊框窗口調(diào)用FloatControlBar實(shí)現(xiàn)工具條的漂浮。
首先,創(chuàng)建一個(gè)微型漂浮邊框窗口,該邊框窗口有一個(gè)泊位條。
然后,在微型邊框窗口的泊位條內(nèi)放置工具條。
MFC設(shè)計(jì)了微型邊框類CMiniFrameWnd,在此基礎(chǔ)上派生出微型泊位邊框窗口類CMiniDockFrameWnd。CMiniDockFrameWnd增加了一個(gè)CDockBar類型成員變量m_wndDockBar,即泊位條。
在CMiniDockFrameWnd對(duì)象被創(chuàng)建時(shí),創(chuàng)建泊位條m_wndDockBar。泊位條m_wndDockBar的父窗口如同CMiniDockFrameWnd的父窗口一樣,是調(diào)用FloatControlBar的邊框窗口,而不是微型泊位邊框窗口。微型邊框窗口和泊位條創(chuàng)建完成之后,調(diào)用ControlBar::DockControlBar泊位工具條在CMiniDockFrameWnd窗口。
具體的代碼實(shí)現(xiàn)略。
聯(lián)系客服