📄 mfc.txt
字号:
//本函数处理以下消息
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, '\n');
}
//复制分离出的文本
#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显示,分隔符号是“\n”。根据这种格式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特征。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -