我从没有在任何一个应用程序中看到过图标的工具提示。有时候查遍了整个帮助 文档也没有明白某个图标是什么意思。如果能在自己的程序中为图标加上工具提 示,一定会使界面的友好性大大增加。本文中以树形控件为例,详细介绍了在VC 中使用MFC提供的机制来实现图标工具提示的方法。
---- 第一步:使控件可以显示工具提示
---- 调用EnableToolTips(TRUE)使一个窗口可以显示工具提示。在什么地方插入 这条代码最好呢?在类的PreSubclassWindow()中。因为不管一个控件如何被创建 ,MFC都会调用此函数。而其他的函数则不一定会被调用。以OnCreate()为例,如 果调用Create()或CreateEx()创建一个控件,OnCreate()会被调用,而如果一个 控件是从对话框资源创建,OnCreate()就不会被调用。
实现代码如下: void CTreeCtrlX::PreSubclassWindow() { CTreeCtrl::PreSubclassWindow();
EnableToolTips(TRUE); }
---- 第二步:重载虚函数OnToolHitTest() ---- MFC调用函数来确定在某个点是否应该显示工具提示。MSDN建议如果鼠标落 在应该显示工具提示的点上,返回值1。这并不完全正确。这个函数应该返回不同 的值来区分窗口中不同的应该显示提示的区域。
---- 在这个函数中,本文只处理鼠标落在节点图标或节点状态图标上的情况。读 者可以按照自己的情况向树的其他元素上添加工具提示。在两种情况下,都要计 算图标的区域,并且把TOOLINFO的uID设为鼠标所在点的树节点的句柄。注意,尽 管对于节点图标和节点状态图标,使用了相同的id,但返回值并不相同。不同的 返回值迫使MFC更新工具提示。
---- 虽然我们可以在此函数中给出工具提示,但因为鼠标的每次移动都会调用此 函数,太多的处理并不是一个好注意,所以我们在其他的函数中处理应该显示什 么提示的问题。
类声明中的代码如下所示: // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CTreeCtrlX) protected: … virtual int OnToolHitTest ( CPoint point, TOOLINFO* pTI ) const; //}}AFX_VIRTUAL
实现代码如下所示: int CTreeCtrlX::OnToolHitTest (CPoint point, TOOLINFO * pTI) const { RECT rect;
UINT nFlags; HTREEITEM hitem = HitTest( point, &nFlags ); if( nFlags & TVHT_ONITEMICON ) { CImageList *pImg = GetImageList( TVSIL_NORMAL ); IMAGEINFO imageinfo; pImg- >GetImageInfo( 0, &imageinfo );
GetItemRect( hitem, &rect, TRUE ); rect.right = rect.left - 2; rect.left -= (imageinfo.rcImage.right + 2);
pTI- >hwnd = m_hWnd; pTI- >uId = (UINT)hitem; pTI- >lpszText = LPSTR_TEXTCALLBACK; pTI- >rect = rect; return pTI- >uId; } else if( nFlags & TVHT_ONITEMSTATEICON ) { CImageList *pImg = GetImageList( TVSIL_NORMAL ); IMAGEINFO imageinfo; pImg- >GetImageInfo( 0, &imageinfo );
GetItemRect( hitem, &rect, TRUE ); rect.right = rect.left - (imageinfo.rcImage.right + 2);
pImg = GetImageList( TVSIL_STATE ); rect.left = rect.right - imageinfo.rcImage.right ;
pTI- >hwnd = m_hWnd; pTI- >uId = (UINT)hitem; pTI- >lpszText = LPSTR_TEXTCALLBACK; pTI- >rect = rect;
// 返回与节点图标不同的值 return pTI- >uId*2; } return -1; }
---- 第三步:处理TTN_NEEDTEXT消息; ---- 加入一个函数处理TTN_NEEDTEXT消息通知。当工具处理控制需要知道应该显 示什么信息时,这条消息被发出。由于上一步中我们给TOOLINFO的lpszText赋值 为LPSTR_TEXTCALLBACK,所以我们要处理这个消息VC的ClassWizard并不支持这条 消息被映射,所以只有我们自己加入这条消息的映射机制加入到MESSAGE_MAP中去 。我们不得不处理这个消息的两个版本,TTN_NEEDTEXTA和TTN_NEEDTEXTA。消息 映射的代码如下所示:
BEGIN_MESSAGE_MAP(CTreeCtrlX, CTreeCtrl) //{{AFX_MSG_MAP(CTreeCtrlX) … //}}AFX_MSG_MAP ON_NOTIFY_EX_RANGE (TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText) ON_NOTIFY_EX_RANGE (TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText) END_MESSAGE_MAP()
下面的代码是加到类声明中: protected: //{{AFX_MSG(CTreeCtrlX) … //}}AFX_MSG afx_msg BOOL OnToolTipText ( UINT id, NMHDR * pNMHDR, LRESULT * pResult ); DECLARE_MESSAGE_MAP()
---- 现在讨论这个函数本身的实现。为了适应不同的语言字符集,ANSI字符集和 UNICODE字符集都必须被处理,处理过程会有些不同。此处对树形控件的本身产生 的ToolTip消息不予处理,过滤掉上述消息的原则是树形控件本身产生的消息的ID 是树形控件窗口的句柄,并且有TTF_IDISHWND标志。根据鼠标位置可以确定应该 给出节点图标还是状态图标的工具提示。本文根据笔者画的图显示了一些无关紧 要的提示,读者做这一步时应该加入一些有意义的提示。当然,本文假定控件包 含节点图标和状态图标。如不包含,计算鼠标位置时要注意 不要计算错误。
BOOL CTreeCtrlX::OnToolTipText ( UINT id, NMHDR * pNMHDR, LRESULT * pResult ) { // 需要处理ANSI和UNICODE两种格式 TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR; TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR; CString strTipText; UINT nID = pNMHDR- >idFrom;
// 不必处理树自己发出的ToolTip消息 if( nID == (UINT)m_hWnd && (( pNMHDR- >code == TTN_NEEDTEXTA && pTTTA- >uFlags & TTF_IDISHWND ) || ( pNMHDR- >code == TTN_NEEDTEXTW && pTTTW- >uFlags & TTF_IDISHWND ) ) ) return FALSE;
// 得到鼠标位置 const MSG* pMessage; CPoint pt; pMessage = GetCurrentMessage(); ASSERT ( pMessage ); pt = pMessage- >pt; ScreenToClient( &pt );
UINT nFlags; HTREEITEM hitem = HitTest( pt, &nFlags ); if( nFlags & TVHT_ONITEMICON ) { int nImage, nSelImage; GetItemImage( (HTREEITEM ) nID, nImage, nSelImage ); switch(nImage) { case 0: strTipText = "叉"; break; case 1: strTipText = "加号"; break; case 2: strTipText = "菱形"; break; } } else { if( (GetItemState( (HTREEITEM ) nID, TVIS_STATEIMAGEMASK ) > >12 ) == 2 ) strTipText.Format( "此节点被选中" ); else strTipText.Format( "此节点未被选中" ); }
#ifndef _UNICODE if (pNMHDR- >code == TTN_NEEDTEXTA) lstrcpyn(pTTTA- >szText, strTipText, 80); else _mbstowcsz(pTTTW- >szText, strTipText, 80); #else if (pNMHDR- >code == TTN_NEEDTEXTA) _wcstombsz(pTTTA- >szText, strTipText, 80); else lstrcpyn(pTTTW- >szText, strTipText, 80); #endif *pResult = 0;
return TRUE; // 消息处理完毕 }
---- 本文程序在Win9x,VC6.0下调试通过。
|