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