我從沒有在任何一個應用程序中看到過圖標的工具提示。有時候查遍了整個幫助 文檔也沒有明白某個圖標是什么意思。如果能在自己的程序中為圖標加上工具提 示,一定會使界面的友好性大大增加。本文中以樹形控件為例,詳細介紹了在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下調試通過。
|