Introduction

CMFCToolTipCtrl provides a very nice looking, multi-line tooltip for use on controls such as buttons, sliders, etc.

I also wanted to use the same class to display dynamic tooltips based on the location of the mouse in the view area (CSrollView class in my case).

This article will describe a very simple way to override the OnDrawLabel method to get the tooltip to draw any text you like at run time.

Background

I have used a tooltip class from this site before but my solution is very simple and uses the nicer looking CMFCToolTipCtrl class.

This code is based on the DlgToolTips example that comes with VS 2010.

Using the Code

Here is the class header CCustomToolTipCtrl.h.

The key thing is the override of OnDrawLabel.

//
#pragma once

/////////////////////////////////////////////////////////////////////////////
// CCustomToolTipCtrl window

class CCustomToolTipCtrl : public CMFCToolTipCtrl
{
// Construction
public:
	CCustomToolTipCtrl();

// Attributes
public:
	int	m_nCurrID;

// Operations
public:

// Overrides

// Implementation
public:
	virtual ~CCustomToolTipCtrl();

protected:
	CSize OnDrawLabel(CDC* pDC,CRect rect,BOOL bCalcOnly);
};

Here is the class declaration CCustomToolTipCtrl.cpp.

Most of the code is taken directly from the original implementation of CMFCToolTipCtrl::OnDrawLabel in MFC. The interesting part is how I produce the two strings, labelText and descriptionText.

//
#include "stdafx.h"
#include "CustomToolTipCtrl.h"

/////////////////////////////////////////////////////////////////////////////
// CCustomToolTipCtrl

CCustomToolTipCtrl::CCustomToolTipCtrl()
{
	m_nCurrID = 0;
}

CCustomToolTipCtrl::~CCustomToolTipCtrl()
{
}

/////////////////////////////////////////////////////////////////////////////
// CCustomToolTipCtrl message handlers

CSize CCustomToolTipCtrl::OnDrawLabel(CDC* pDC, CRect rect, BOOL bCalcOnly)
{
//	TRACE("ENTER CCustomToolTipCtrl::OnDrawLabel bCalcOnly=%d\n", bCalcOnly);
	ASSERT_VALID(pDC);

	// my special tooltip drawing system for context sensitive tooltips 
	// in view (track)
	POINT cursor;
	GetCursorPos(&cursor);
	GetActiveView()->ScreenToClient(&cursor);
	CString labelText, descriptionText;
	GetActiveView()->GetToolTipLabelText(cursor, labelText, descriptionText);

	CSize sizeText(0, 0);

	BOOL bDrawDescr = m_Params.m_bDrawDescription && !m_strDescription.IsEmpty();

	CFont* pOldFont = (CFont*) pDC->SelectObject(m_Params.m_bBoldLabel && 
		bDrawDescr ? &afxGlobalData.fontBold : &afxGlobalData.fontTooltip);

	if (labelText.Find(_T('\n')) >= 0) // Multi-line text
	{
		UINT nFormat = DT_NOPREFIX;
		if (bCalcOnly)
		{
			nFormat |= DT_CALCRECT;
		}

		if (m_pRibbonButton != NULL)
		{
			nFormat |= DT_NOPREFIX;
		}

		int nHeight = pDC->DrawText(labelText, rect, nFormat);
		sizeText = CSize(rect.Width(), nHeight);
	}
	else
	{
		if (bCalcOnly)
		{
			sizeText = pDC->GetTextExtent(labelText);
		}
		else
		{
			UINT nFormat = DT_LEFT | DT_NOCLIP | DT_SINGLELINE;

			if (!bDrawDescr)
			{
				nFormat |= DT_VCENTER;
			}

			if (m_pRibbonButton != NULL)
			{
				nFormat |= DT_NOPREFIX;
			}

			sizeText.cy = pDC->DrawText(labelText, rect, nFormat);
			sizeText.cx = rect.Width();
		}
	}

	pDC->SelectObject(pOldFont);

	SetDescription (descriptionText);

	return sizeText;
}

The code below gets the current absolute cursor position, then converts it to a point relative to the current view.

Then GetToolTipLabelText fills in the two cstrings with text based on the location of the cursor in the view window.

//
// my special tooltip drawing system for context sensitive tooltips in view (track)
POINT cursor;
GetCursorPos(&cursor);
GetActiveView()->ScreenToClient(&cursor);
CString labelText, descriptionText;
GetActiveView()->GetToolTipLabelText(cursor, labelText, descriptionText);

This line draws the text for the top part of the tooltip:

//
pDC->DrawText(labelText, rect, nFormat)

This line draw the text for the bottom part of the tooltip:

//
SetDescription (descriptionText);

Now, how to hook it up to your application. In the view's constructor:

// Create the ToolTip control.
m_ToolTip.Create(this);
m_ToolTip.Activate(TRUE);

m_ToolTip is of type CCustomToolTipCtrl:

In the view's OnInitialUpdate:

//
CMFCToolTipInfo params;
params.m_bVislManagerTheme = TRUE;
m_ToolTip.SetParams (¶ms);
m_ToolTip.AddTool (this, _T("BASE LABLE"));
m_ToolTip.SetDelayTime(3000);		// 3 seconds before tooltips pop up in view

You will need a PreTranslateMessage to force events to get routed to the tooptip class (this is the critical bit of the whole thing):

//
BOOL CTabTraxView::PreTranslateMessage(MSG* pMsg)
{
   	switch (pMsg->message)
	{
	case WM_KEYDOWN:
	case WM_SYSKEYDOWN:
	case WM_LBUTTONDOWN:
	case WM_RBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_LBUTTONUP:
	case WM_RBUTTONUP:
	case WM_MBUTTONUP:
	case WM_MOUSEMOVE:
		m_ToolTip.RelayEvent(pMsg);
		break;
	}

	return CScrollView::PreTranslateMessage(pMsg);
}

Finally, at the end of my view's OnMouseMove, I need to get rid of the tooltip (not sure why it doesn't do it automatically but this works).

//
m_ToolTip.Pop();

And that's about it, less than 100 lines of code and it uses the built in features in MFC.

Points of Interest

I should point out that I'm not really an expert in MFC programming. I just figured this out by debugging the DlgToolTip app so I can't guarantee that this is the best or safest way to do it, but for me it seems to work fine.

History

  • 28th May, 2011: Initial version
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"