程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> MFC教程(13)-MFC工具條和狀態欄(2)

MFC教程(13)-MFC工具條和狀態欄(2)

編輯:關於VC++

消息WM_POPMESSAGESTRING用來重新設置狀態欄。

這兩個消息對應的消息處理函數分別是OnSetMessageString和OnPopMessageString,OnSetMessageString和OnPopMessageString分別實現如下:

OnSetMessageString
LRESULT CFrameWnd::OnSetMessageString(WPARAM wParam, LPARAM lParam)
{
//最近一次被顯示的消息字符串IDS(一個消息對應的字符串)
UINT nIDLast = m_nIDLastMessage;
m_nFlags &= ~WF_NOPOPMSG;
//得到狀態欄
CWnd* pMessageBar = GetMessageBar();
if (pMessageBar != NULL)
{
LPCTSTR lpsz = NULL;
CString strMessage;
//設置狀態欄文本
if (lParam != 0) //指向一個字符串
{
ASSERT(wParam == 0); // can't have both an ID and a string
lpsz = (LPCTSTR)lParam; // set an explicit string
}
else if (wParam != 0)//一個字符串資源IDS
{
//打印預覽時映射SC_CLOSE成AFX_IDS_PREVIEW_CLOSE;
if (wParam == AFX_IDS_SCCLOSE && m_lpfnCloseProc != NULL)
wParam = AFX_IDS_PREVIEW_CLOSE;
//得到資源ID所標識的字符串
GetMessageString(wParam, strMessage);
lpsz = strMessage;
}
//在狀態欄中顯示文本
pMessageBar->SetWindowText(lpsz);
// 根據最近一次選擇的消息更新狀態條所屬窗口的有關記錄
CFrameWnd* pFrameWnd = pMessageBar->GetParentFrame();
if (pFrameWnd != NULL)
{
//記錄最近一次顯示的消息字符串
pFrameWnd->m_nIDLastMessage = (UINT)wParam;
//記錄最近一次Tracking的命令ID和字符串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函數直接或者從ID從字符串資源中得到字符串指針。如果是從ID得到字符串指針,則函數GetMessageString被調用。

和命令ID對應的字符串由兩部分組成,前一部分用於在狀態欄顯示,後一部分用於Tooltip顯示,分隔符號是“ ”。例如,字符串ID_APP_EXIT(對應“退出”菜單、按鈕)是“Exit Application Exit”,當鼠標落在“退出”按鈕上時,狀態欄顯示“Exit Application”,Tooltip顯示“Exit”。根據這種格式,GetMessageString分離出第一部分的文本信息。至於第二部分的用途將在討論Tooltip的章節將用到。

得到了字符串之後,OnSetMessageString調用狀態欄的SetWindowText函數。SetWindowText導致消息WM_SETTEXT消息發送給狀態欄,狀態欄的消息處理函數OnSetText被調用,實際上等於調用了SetPaneText(0, lpsz),即在狀態欄的第0格中顯示字符串lpsz的信息。對於工具欄來說,SetWindowText可以認為是SetPaneText(0, lpsz)的簡化版本。

順便指出,pMessageBar->GetParentFrame()返回主邊框窗口,即使pMessageBar指向漂浮的工具條。關於泊位和漂浮,見後面13.2.5節的描述。

關於OnSetText,其實現如下:

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;
}
OnPopMessageString
LRESULT CFrameWnd::OnPopMessageString(WPARAM wParam,
LPARAM lParam)
{
//WF_NOPOPMSG表示邊框窗口不處理WM_POPMESSAGESTRING
if (m_nFlags & WF_NOPOPMSG)
return 0;
//調用OnSetMessageString
return SendMessage(WM_SETMESSAGESTRING, wParam, lParam);
}

一般,在清除狀態欄消息時,發送WM_POPMESSAGESTRING,通過消息參數wParam指定一個字符串資源,其ID 為AFX_IDS_IDLEMESSAGE,對應的字符串是“Ready”。

狀態欄顯示菜單項的提示信息

狀態欄的一個重要作用是顯示菜單命令或者工具條按鈕的提示信息。本節討論如何顯示菜單命令的提示信息,關於工具條按鈕在這方面的討論見後面13.2.4.4章節。

顯示菜單命令的提示信息,就是每當一個菜單項被選中之後,在狀態欄顯示該菜單的功能、用法等信息。這些信息以字符串資源的形式保存,字符串ID對應於菜單項的命令ID。

所以,必須處理菜單選擇消息WM_MENUSELECT。CFrameWnd實現了消息處理函數OnMenuSelect,其實現如下:

void CFrameWnd::OnMenuSelect(UINT nItemID,
UINT nFlags, HMENU /*hSysMenu*/)
{
CFrameWnd* pFrameWnd = GetTopLevelFrame();
ASSERT_VALID(pFrameWnd);
//跟蹤被選中的菜單項
if (nFlags == 0xFFFF)
{
//取消菜單操作
m_nFlags &= ~WF_NOPOPMSG;
if (!pFrameWnd->m_bHelpMode)
m_nIDTracking = AFX_IDS_IDLEMESSAGE;
else
m_nIDTracking = AFX_IDS_HELPMODEMESSAGE;
//在狀態欄顯示
SendMessage(WM_SETMESSAGESTRING, (WPARAM)m_nIDTracking);
ASSERT(m_nIDTracking == m_nIDLastMessage);
// update right away
CWnd* pWnd = GetMessageBar();
if (pWnd != NULL)
pWnd->UpdateWindow();
}
else
{
//選中分隔欄、Popup子菜單或者沒有選中一個菜單項
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
{
//系統菜單的菜單項被選中
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)
{
//如果選中的菜單項表示一個MDI子窗口
m_nIDTracking = AFX_IDS_MDICHILD;
}
else
{
//選中了一個菜單項
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的作用在於跟蹤當前選中的菜單項,把菜單項對應的ID保存在CFrameWnd的成員變量m_nIDTracking中。

如果菜單項沒有選中,或者選中的是一個子菜單,則設置nIDTracking為0。

如果選中的是系統菜單,則把系統菜單ID轉換成一個對應的命令ID;保存該值到nIDTracking中。

如果選中的菜單是MDI子窗口創建時添加的(用來表示MDI子窗口),則轉換菜單ID為AFX_IDS_MDICHILD,所有對應MDI子窗口的菜單項都使用AFX_IDS_MDICHILD,保存該值到nIDTracking中。

其他情況,就是選中菜單項的ID,把它保存到nIDTracking中。

跟蹤被選擇的菜單項並保存其ID在m_nIDTracking中,OnEnterIdle將用到m_nIDTracking。OnEnterIlde是消息WM_ENTERIDLE的處理函數,CFrameWnd的實現如下。

void CFrameWnd::OnEnterIdle(UINT nWhy, CWnd* pWho)
{
CWnd::OnEnterIdle(nWhy, pWho);
//若不是因為菜單選擇進入該函數
//或者當前跟蹤到的菜單項ID是最近一次處理的,則返回
if (nWhy != MSGF_MENU || m_nIDTracking == m_nIDLastMessage)
return;
//將發送消息WM_SETMESSAGETEXT
//在狀態欄顯示m_nIDTracking對應的字符串
SetMessageText(m_nIDTracking);
ASSERT(m_nIDTracking == m_nIDLastMessage);
}

當一個對話框或者菜單被顯示的時候,Windows發送WM_ENTERIDLE消息。消息參數wParam取值為MSGF_DIALOGBOX或者MSGF_MENU。前者表示顯示對話框時發送該消息,這時消息參數lParam表示對話框的句柄;後者表示顯示菜單時發送該消息,這時消息參數lParam表示菜單的句柄。

經過消息映射,wParam的值傳遞給OnEnterIdle的參數nWhy,參數lParam的值傳給參數who。如果參數1取值為MSGF_MENU,並且OnEnterIdle最近一次在菜單顯示被調用時的菜單ID不同於這一次,則調用SetMessageText在狀態欄顯示對應ID命令的字符串,並且記錄當前菜單ID到變量m_nIDTracking中(見消息處理函數OnSetMessageText)。

這樣,在菜單選擇期間,用戶選擇的菜單項ID被OnMenuSelect記錄,在消息WM_ENTERIDLE處理時在狀態欄顯示ID命令的提示。

控制條的消息分發處理

工具條(包括對話框工具條)是一個子窗口,它們可以響應各種消息。如果按標准的Windows消息和命令消息的分發途徑,一些消息不能送到擁有工具條的邊框窗口,因為這些消息都將被工具條(對話框工具條)處理掉。所以,CControlBar覆蓋了虛擬函數PreTranslateMessage和WindowProc以便實現特定的消息分發路徑。

WindowProc

CControlBar 的WindowProc實現了如下的消息分發路徑:

用戶對控制條的輸入消息或者分發給CControlBar及其派生類處理,或者送給擁有控制條的邊框窗口處理,或者送給Windows控制“窗口類”的窗口過程處理。

WindowProc的實現如下:

LRESULT CControlBar::WindowProc(UINT nMsg,
WPARAM wParam, LPARAM lParam)
{
ASSERT_VALID(this);
LRESULT lResult;
switch (nMsg)
{
//本函數處理以下消息
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;
}

從上述實現可以看出,對於case范圍內的一些消息,如WM_COMMAND、WM_NOTIFY等,控制條如果不能處理,則優先分發給其父窗口(邊框窗口)處理,然後進入缺省處理,對於其他消息直接調用基類CWnd的實現(缺省處理)。基於這樣的機制,可以把用戶對工具條按鈕或者對話框工具條內控制的操作解釋成相應的命令消息,執行對應的命令處理。

對於工具條,當用戶選中某個按鈕時(鼠標左鍵彈起,消息是WM_LBUTTONUP),工具條窗口接收到WM_LBUTTONUP消息,該消息不在CControlBar::WindowProc特別處理的消息范圍內,於是進行缺省處理,也就是說,把該消息派發給控制條對應的Windows控制的窗口過程處理(即被MFC統一窗口過程所取代的原窗口過程),該窗口過程則把該消息轉換成一條命令消息WM_COMMAND,命令ID就是選中按鈕對應的ID,然後,發送該命令消息給擁有工具條的邊框窗口,導致相應的命令處理函數被調用。

對於對話框工具條,當工具條的某個控制子窗口被選中之後,則產生一條命令通知消息WM_COMMAND,wParam是控制子窗口的ID。CControlBar::WindowProc處理該消息。WindowProc首先調用OnWndMsg把消息發送給對話框工具條或者對話框工具條的基類處理,如果沒有被處理的話,則OnWndMsg返回FALSE。接著,WindowPoc把命令消息傳遞給父窗口(邊框窗口)處理。由於工具條的控制窗口的ID對應的是命令ID,所以,這條WM_COMMAND消息傳遞給邊框窗口時,被解釋成一個命令消息,相應的命令處理函數被調用。

PreTranslateMessage

CControlBar覆蓋PreTranslateMessage函數,主要是為了在光標落在工具條按鈕上時顯示FLYBY信息,並且讓對話框工具條過濾Dialog消息。

BOOL CControlBar::PreTranslateMessage(MSG* pMsg)
{
ASSERT_VALID(this);
ASSERT(m_hWnd != NULL);
//過濾Tooltip消息
if (CWnd::PreTranslateMessage(pMsg))
return TRUE; //是Tooltip消息,已經被處理
UINT message = pMsg->message;
//控制條的父窗口,對工具條和對話框工具條,總是創建它的邊框窗口
CWnd* pOwner = GetOwner();
//必要的話,在狀態條顯示工具欄按鈕的提示
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();
//確認鼠標在工具欄的哪個按鈕上
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;
//更新狀態欄的提示信息
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之前調用邊框窗口的PreTranslateMessage,
//給邊框窗口一個處理快捷鍵的機會
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();
}
//過濾給對話框的消息和來自子窗口的消息
return PreTranslateInput(pMsg);
}

函數PreTranslateMessage主要是針對工具欄的,用來處理工具欄的CBRS_FLYBY特征。

對於對話框工具欄,也可以有CBRS_FLYBY特征。但在這種情況下,還需要把一些用戶鍵盤輸入解釋成對話框消息。為了防止快捷鍵被解釋成對話框消息,在調用函數PreTranslateInput之前,必須調用所有父邊框窗口的PreTranslateMessage,給父邊框窗口一個機會處理用戶的輸入消息,判斷快捷鍵是否被按下。

關於Tooltip和本PreTranslateMessage函數處理Tooltip的詳細解釋見下一節的討論。

Tooltip

工具條(或對話框工具條)如果指定了CBRS_TOOLTIPS風格(創建時指定或者創建後用SetBarStyle設置),則當鼠標落在某個按鈕上(或者對話框的子控制窗口)時,在鼠標附近彈出一個文本框──Tooltip提示窗口。

如果還指定了CBRS_FLYBY風格,則還在狀態欄顯示和按鈕(或子控制窗口)ID對應的字符串信息。當然,鼠標左鍵在某個按鈕(或子控制窗口)按下時,也要在狀態欄顯示按鈕的提示信息,當左鍵彈起時,則重置狀態欄的狀態。

如前所述,Tooltip窗口是Windows控制窗口。MFC使用了CToolTipCtrl類封裝Tooltip的HWND窗口。在一個線程的生存期間,至多擁有一個Tooltip窗口,該窗口對象的指針保存在線程狀態的成員變量m_pToolTip中。線程狀態類AFX_THREAD_STATE的析構函數如果檢測到m_pToolTip,則銷毀MFC窗口對象和相應的Windows窗口對象。

CWnd對Tooltip消息的預處理

為了支持Tooltip顯示,CWnd提供了以下函數:EnableTooltip,CancelTooltip,PreTranslateMessage,FilterTooltipMessage,OnToolHitTest。

EnableTooltip設置CBRS_TOOLTIP風格,相反CancelTootip取消這種風格。

PreTranslateMessage調用了FilterTooltipMessage過濾Tooltip消息。

OnToolHitTest是一個由CWnd定義的虛擬函數。CToolBar通過覆蓋該函數,來檢測對話框工具欄的控制子窗口或者工具欄按鈕是否被選中、哪個被選中。

CWnd的PreTranslateMessage在4.5節討論過,它的實現如下:

BOOL CWnd::PreTranslateMessage(MSG* pMsg)
{
//處理Tooltip消息
AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();
if (pModuleState->m_pfnFilterToolTipMessage != NULL)
//導致調用FilterTooltipMessage
(*pModuleState->m_pfnFilterToolTipMessage)(pMsg, this);
//不是Tooltip消息
return FALSE;
}

至於為什麼MFC在模塊狀態中保存一個處理Tooltip消息的函數地址,通過該函數調用FilterTooltipMessage,是因為Tooltip窗口是模塊線程局部有效的。

FilterTooltipMessage檢測是否是Tooltip消息。如果是,在必要時創建一個CTooltipCtrl對象和對應的HWND,調用OnToolHitTest確定被選中的按鈕或者控制的ID,接著彈出Tooltip窗口。

其他函數和CTooltipCtrl這裡不作詳細論述了。

處理TTN_NEEDTEXT通知消息

Tooltip窗口在彈出之前,它給工具條(或者對話框工具欄)的父窗口發送通知消息TTN_NEEDTEXT,請求得到要顯示的文本。

CFrameWnd類處理了TTN_NEEDTEXT通知消息,消息處理函數是OnToolTipText。

消息映射的定義:

ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)

ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)

這裡,使用了擴展消息映射宏把子窗口ID在0和0xFFFF之間的控制條窗口的通知消息TTN_NEEDTEXTA和TTN_NEEDTEXTW映射到函數OnToolTipText。

消息映射的實現:

BOOL CFrameWnd::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)
{
ASSERT(pNMHDR->code == TTN_NEEDTEXTA ||
pNMHDR->code == TTN_NEEDTEXTW);
//讓上一層的邊框窗口優先處理該消息
if (GetRoutingFrame() != NULL)
return FALSE;
//分ANSI and UNICODE兩個處理版本
TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
TCHAR szFullText[256];
CString strTipText;
UINT nID = pNMHDR->idFrom;
//如果idFrom是一個子窗口,則得到其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對應的字符串
AfxLoadString(nID, szFullText);
//從上面得到的字符串中取出Tooltip使用的文本
AfxExtractSubString(strTipText, szFullText, 1, '
');
}
//復制分離出的文本
#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是一個擴展映射宏定義的消息處理函數,所以有一個UINT參數並且返回BOOL類型的值。不過,由於第二個參數(NMHDR)的idFrom域包含了有關信息,所以第一個UINT類型的參數沒有用上。

OnToolTipText也是一個處理通知消息的例子。其中,通知參數wParam的結構如4.4.4.2節所述,具體如下:

typedef struct {
NMHDR hdr; //WM_NOTIFY消息要求的頭
LPTSTR lpszText; //接收工具條按鈕對應文本的緩沖區
WCHAR szText[80]; //接收Tooltip顯示文本的緩沖區
HINSTANCE hinst; //包含了szText的實例句柄
UINT uflags; //標識了NMHDR的idFrom成員的意義
} TOOLTIPTEXT, FAR *LPTOOLTIPTEXT;

uflags如果等於TTF_IDISHWND,則表示通知消息來自對話框工具條的一個子窗口,而不是包含工具條按鈕。

OnToolTipText根據子窗口ID或者工具條按鈕對應的ID,得到字符串ID。如前所述,字符串ID由兩部分組成,第二部分用於Tooltip顯示,分隔符號是“ ”。根據這種格式OnToolTipText分離出Tooltip文本。

得到了Tooltip文本之後,可以有三種方法返回文本信息:把文本信息復制到szText緩沖區;把文本地址復制到lpszText;復制字符串資源的ID到lpszText、復制包含資源的實例句柄到hint。本函數采用了第一種方法。

在得到了返回的Tooltip文本之後,該文本在Tooltip窗口中被顯示出來。

其他的OnToolHist等函數的實現不作詳細的解釋了。下面,討論CBRS_FLYBY風格的實現。

CBRS_FLYBY風格的實現

CBRS_FLYBY是MFC提供的特征。當鼠標落在工具條按鈕(或者對話框工具條的子窗口)上且穩定300ms後,在狀態欄顯示對應的提示信息。如果選中某個按鈕或者子窗口(鼠標左鍵按下),則在相應命令消息處理之前在狀態欄顯示有關提示信息,之後(鼠標左鍵彈起),重新設置狀態欄的狀態信息。

為了支持這種特征,CControlBar覆蓋虛擬函數PreTranslateMessage來處理和CBRS_FLYBY相關的消息,該函數前面已經討論過,這裡解釋它如何處理CBRS_FLYBY特征。

如果同時具備

條件1:控制條具有CBRS_FLYBY特征或者當前消息是WM_LBUTTONUP或者WM_LBUTTONDOWN。

條件2:當前消息是鼠標消息(在WM_MOUSEFIRST和WM_MOUSELAST之間或者在WM_NCMOUSEFIRST和WM_NCMOUSELAST之間)。

則進行FLYBY處理。

首先,調用OnToolHitTest測試用戶是否選中了工具條的按鈕或者子窗口;

如果沒有按鈕或者子窗口被選中,則重新設置狀態欄的狀態,取消曾經設置的Check定時器。重置狀態欄的狀態時調用了SetStatusText(int nHit)函數,它是CControlBar內部使用的函數,若nHit等於-1,它向父窗口發送WM_POPMESSAGETEXT,消息參數是AFX_IDS_IDLEMESSAGE,結果導致狀態欄顯示“Ready”字樣;否則發送WM_SETMESSAGETEXT消息,wParm設置為nHit,結果導致在狀態欄顯示ID為nHit的字符串。

如果有按鈕或者子窗口被選中,若左鍵彈起,則重新設置狀態欄信息,取消Wait定時器,並重新設置Check定時器,定時是200ms;若左鍵按下,則在狀態欄顯示消息ID對應的提示信息;若是其他鼠標消息,如果當前鼠標所在按鈕(子窗口)不同於最近一次,則取消Check定時器,重新設置Wait定時器,定時300毫秒。

CControlBar覆蓋了消息處理函數OnTimer,在指定時間之後,檢查鼠標位置,如果鼠標還在某個按鈕或者子窗口上,則在狀態條顯示提示信息。Wait定時器在等待之後准備在狀態條顯示信息,觸發一次後被取消;Check定時器在等待之後,判斷是否需要取消狀態條當前顯示的信息,重新設置狀態條,若這樣的話,同時也取消Check定時器。

注意,這些鼠標消息被處理之後,並沒有終止,它們將繼續被發送給控制條的窗口過程處理。

至此,CBRS_FLYBY特征的支持實現描述完畢。

禁止和允許

在MFC下,工具條、狀態條還有一個重要的特征,就是自動地根據條件禁止或者允許使用某個按鈕、窗格等。在4.4.5節命令消息的處理中,曾詳細討論了其實現原理,現在,詳細地分析所涉及函數是如何實現的。有關的消息處理函數和虛擬函數如下。

處理WM_INITIALUPDATE消息的OnInitialUpdate;

處理WM_IDLEUPDATECMDUI消息的OnIdleUpdateCmdUI;

虛擬函數OnUpdateCmdUI。

回顧5.3.3.5節,在邊框窗口的創建之後,給所有的子窗口發送初始化消息,控制子窗口用OnInitialUpdate響應它,調用OnIdleUpdateCmdUI完成狀態的初始化。

OnIdleUpdateCmdUI還在IDLE處理時進行狀態的更新處理,它生成用於處理狀態更新消息的命令目標pTarget,然後調用虛擬函數OnUpdateCmdUI(pTarget,…)來更新工具欄或者狀態欄的狀態。

CControlBar的子類都實現了自己的OnUpdateCmdUI函數,用該函數生成適當的CCmdUI對象state,然後調用CCmdUI的DoUpdate(pTarget,…)給pTarget所指對象發送狀態更新消息。為了完成具體的狀態更新,從CCmdUI派生出CToolCmdUI和CStatusCCmUI,它們實現了自己的Enable、SetCheck等等。

初始化控制窗口

CControlBar使用OnInitialUpdate消息處理函數初始化控制窗口的狀態。

void CControlBar::OnInitialUpdate()
{
//在窗口顯示之前,更新狀態
OnIdleUpdateCmdUI(TRUE, 0L);
}

CControlBar實現了OnInitialUpdate函數,通過它來處理WM_INITIALUPDATE消息。各個子類不必覆蓋該消息處理函數。

處理Idle消息更新工具條狀態

CControlBar使用OnIdleUpdateCmdUI消息處理函數處理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)))
{
//得到父邊框窗口,狀態更新消息將發送給它
CFrameWnd* pTarget = (CFrameWnd*)GetOwner();
if (pTarget == NULL || !pTarget->IsFrameWnd())
pTarget = GetParentFrame();
if (pTarget != NULL)
OnUpdateCmdUI(pTarget, (BOOL)wParam);
}
return 0L;
}

OnIdleUpdateCmdUI或者在初始化時被OnInitialUpdate調用,或者作為消息處理函數來處理WM_IDLEUPDATECMDUI消息。

CControlBar實現了OnIdleUpdateCmdUI函數,把具體的用戶界面更新動作委托給虛擬函數OnUpdateCmdUI完成。

由於各個用戶界面的特殊性,所以CControlBar本身沒有實現OnUpdateCmdUI,而是留給各個派生類去實現。例如,CToolBar覆蓋了OnUpdateCmdUI,其實現如下:

void CToolBar::OnUpdateCmdUI(CFrameWnd* pTarget, BOOL bDisableIfNoHndler)
{
//定義一個CCmdUI對象,CToolCmdUI派生於CCmdUI
CToolCmdUI state;
//給CCmdUI的各個成員賦值
state.m_pOther = this;
//得到總的按鈕數目
state.m_nIndexMax = (UINT)DefWindowProc(TB_BUTTONCOUNT, 0, 0);
//逐個按鈕進行狀態更新
for (state.m_nIndex = 0; state.m_nIndex < state.m_nIndexMax; state.m_nIndex++)
{
//獲取按鈕狀態信息
TBBUTTON button;
_GetButton(state.m_nIndex, &button);
//得到按鈕的ID
state.m_nID = button.idCommand;
// ignore separators
if (!(button.fsStyle & TBSTYLE_SEP))
{
//優先讓CToolBar對象處理狀態更新消息
if (CWnd::OnCmdMsg(state.m_nID,
CN_UPDATE_COMMAND_UI, &state, NULL))
continue;//處理了更新消息,更新下一個按鈕
//CToolBar沒有處理,將發送給pTarget處理狀態更新消息
//第二個參數bDisableIfNoHndler往下傳
state.DoUpdate(pTarget, bDisableIfNoHndler);
}
}
//更新加到控制條中的對話框控制的狀態
UpdateDialogControls(pTarget, bDisableIfNoHndler);
}

CToolBar的OnUpdateCmdUI函數完成工具條按鈕的狀態更新。它接受兩個參數,參數1表示接收狀態更新命令消息的對象,由CControlBar的函數OnIdleUpdateCmdUI傳遞過來,一般是邊框窗口對象;參數2表示如果某條命令消息沒有處理函數時,對應的用戶接口對象是否被禁止。

OnUpdateCmdUI通過發送狀態更新通知消息,逐個更新按鈕的狀態。更新消息首先讓工具條對象處理,如果沒有處理的話,送給邊框窗口對象處理,導致狀態更新命令消息的處理函數被調用,參見4.4.5節。

CStatusBar的OnUpdateCmdUI類似於此。

CDialogBar的OnUpdateCmdUI則調用了虛擬函數UpdateDialogControls來進行狀態更新,CWnd提供了該函數的實現,過程類似於CToolBar的函數OnUpdateCmdUI。

菜單項的自動更新

那麼,菜單項的自動更新如何實現的呢?OnInitMenuPopup在菜單項狀態的自動更新中曾經被提到,其實現如下:

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)中彈出,如果這樣
//則設置m_pParentMenu指向頂層菜單,否則m_pParentMenu
//為空,表示它是一個二級彈出菜單
HMENU hParentMenu;
//是否是浮動式的彈出菜單(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);
//確定頂層窗口的菜單是否包含本菜單項
for (int nIndex = 0; nIndex < nIndexMax; nIndex++)
{
if (::GetSubMenu(hParentMenu, nIndex) == pMenu->m_hMenu)
{
//頂層窗口菜單是本菜單的父菜單
state.m_pParentMenu = CMenu::FromHandle(hParentMenu);
break;
}
}
}
}
//本菜單的菜單項(menu item)數量
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 == 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)
{
// 可能是一個popup菜單,得到其第一個子菜單項目
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菜單的子菜單項
}
//popup菜單不會被自動的禁止
state.DoUpdate(this, FALSE);
}
else
{
//正常的菜單項,若邊框窗口的m_bAutoMenuEnable設置為
//TURE且菜單項非系統菜單,則自動enable/disable該菜單項
state.m_pSubMenu = NULL;
state.DoUpdate(this, m_bAutoMenuEnable && state.m_nID < 0xF000);
}
//經過菜單狀態的更新處理,可能增加或刪除了一些菜單項
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;
}
}

菜單彈出之前,發送WM_INITMENUPOPUP消息,OnInitMenuPopup消息處理函數被調用,逐個更新菜單項目(menu item)的狀態。程序員可以處理它們對應的狀態更新消息,禁止/允許菜單項目被使用(disable/enable),在菜單項目上打鉤或者取消(checked/unchecked),等等。

顯示或者隱藏工具欄和狀態欄

這裡討論顯示或者隱藏工具欄、狀態欄的操作,以及工具欄、狀態欄被顯示/隱藏時,相關的兩個菜單項ID_VIEW_STATUS_BAR、ID_VIEW_TOOLBAR的狀態更新。這兩個菜單命令及對應的狀態更新命令是標准命令消息所包含的。MFC邊框窗口實現了菜單命令消息的處理和菜單項狀態的更新。

CFrameWnd提供了OnBarCheck來響應與ID_VIEW_STATUS_BAR、ID_VIEW_TOOLBAR菜單項對應的命令。

消息映射:

ON_COMMAND_EX(ID_VIEW_STATUS_BAR, OnBarCheck)

ON_COMMAND_EX(ID_VIEW_TOOLBAR, OnBarCheck)

這裡,使用了擴展命令消息映射宏把ID_VIEW_STATUS_BAR和ID_VIEW_TOOLBAR命令映射給同一個函數OnBarCheck處理。

OnBarCheck函數的實現:

BOOL CFrameWnd::OnBarCheck(UINT nID)
{
ASSERT(ID_VIEW_STATUS_BAR == AFX_IDW_STATUS_BAR);
ASSERT(ID_VIEW_TOOLBAR == AFX_IDW_TOOLBAR);
//得到工具條或者狀態條
CControlBar* pBar = GetControlBar(nID);
if (pBar != NULL)
{
//若控制條可見,則隱藏它;否則,顯示它
ShowControlBar(pBar, (pBar->GetStyle() & WS_VISIBLE) == 0, FALSE);
//處理完畢
return TRUE;
}
//可以讓下一個命令目標繼續處理
return FALSE;
}

由於是擴展映射宏定義的消息處理函數,所以OnBarCheck函數有一個UINT類型的參數和一個BOOL返回值。

當用戶從“View”菜單選擇打了鉤的“Toolbar”時,消息處理函數OnBarCheck被調用,參數就是菜單項的ID號ID_VIEW_TOOLBAR,它等於工具條的子窗口IDAFX_IDW_TOOLBAR。處理結果,工具條被隱藏;當再次選擇該菜單項則工具條被顯示。

處理狀態條的過程類似於工具條的處理。

ShowControlBar是CFrameWnd的成員函數,參數1表示控制條對象指針,參數2表示顯示(TRUE)或者隱藏(FALSE),參數3表示是立即顯示(FALSE)或者延遲顯示(TRUE)。

如果工具條或者狀態條被隱藏,則相應的菜單項ID_VIEW_STATUS_BAR 或者ID_VIEW_TOOLBAR 變成uncheked(菜單項被標記為沒有選擇),否則,checked(菜單項被標記選擇)。CFrameWnd實現了這兩個菜單項的狀態更新處理,列舉其中一個如下:

聲明處理ID_VIEW_TOOLBAR的狀態更新消息:

ON_UPDATE_COMMAND_UI(ID_VIEW_TOOLBAR, OnUpdateControlBarMenu)

函數的實現:

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的成員函數,用來返回邊框窗口的指定ID的控制條對象(指定ID是控制條的子窗口ID)。

泊位和漂浮

工具條可以泊位在邊框窗口的任一邊(上、下、左、右),或者漂浮在屏幕上的任何地方。

實現泊位的方法

首先,邊框窗口調用CFrameWnd::EnableDocking函數使控制條泊位在邊框窗口中有效,指明在邊框窗口的哪邊接受泊位。如果想在任何邊都可以泊位,則使用參數CBRS_ALIGN_ANY。

然後,工具條調用ControlBar::EnableDocking使泊位對工具條有效,如果在調用ControlBar::EnableDocking時指定的泊位目的邊和邊框窗口能夠泊位的邊不符合,那麼工具條不能泊位,它將漂浮。

最後,邊框窗口調用CFrameWnd::DockControlBar泊位工具條。

泊位後形成窗口層次關系

邊框窗口、泊位條、工具條的包含關系如下:

邊框窗口

泊位條1

工具條1

工具條2

泊位條2

邊框窗口包含1到4個泊位條子窗口,每個泊位條包含若干個控制條子窗口。

泊位的實現

CFrameWnd::EnableDocking指定哪邊接受泊位,則為泊位准備一個泊位條。泊位條用CDockBar描述,派生於CControlBar。如果指定任何邊都可以泊位,則創建四個CDockBar對象和對應的HWND窗口。然後,調用ControlBar::EnableDocking在對應的泊位條內安置工具條。

MFC設計了CDockBar類和CFrameWnd的一些函數來實現泊位,具體代碼實現在此不作詳細討論。

實現漂浮工具條的方法:

邊框窗口調用FloatControlBar實現工具條的漂浮。

漂浮的實現:

首先,創建一個微型漂浮邊框窗口,該邊框窗口有一個泊位條。

然後,在微型邊框窗口的泊位條內放置工具條。

MFC設計了微型邊框類CMiniFrameWnd,在此基礎上派生出微型泊位邊框窗口類CMiniDockFrameWnd。CMiniDockFrameWnd增加了一個CDockBar類型成員變量m_wndDockBar,即泊位條。

在CMiniDockFrameWnd對象被創建時,創建泊位條m_wndDockBar。泊位條m_wndDockBar的父窗口如同CMiniDockFrameWnd的父窗口一樣,是調用FloatControlBar的邊框窗口,而不是微型泊位邊框窗口。微型邊框窗口和泊位條創建完成之後,調用ControlBar::DockControlBar泊位工具條在CMiniDockFrameWnd窗口。

具體的代碼實現略。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved