Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version
Go to top

Undocumented Visual C++

, 17 Sep 2000
Spelunking in the Badlands of MSDEV
////////////////////////////////////////////////////////////////////////////
// TitleTip.cpp : implementation file
//
// Adapted from code written by Zafir Anjum
//
// Modifed 10 Apr 1999  Now accepts a LOGFONT pointer and 
//					    a tracking rect in Show(...)  (Chris Maunder)
//         18 Apr 1999  Resource leak in Show fixed by Daniel Gehriger
//          7 Jan 2000  Added multiline capabilities, and the ability to
//                      specify the maximum length of the tip (Mark Findlay)
   

#include "stdafx.h"
#include "TitleTip.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CTitleTip
	
CTitleTip::CTitleTip()
{
	// Register the window class if it has not already been registered.
	WNDCLASS wndcls;
	HINSTANCE hInst = AfxGetInstanceHandle();
	if(!(::GetClassInfo(hInst, TITLETIP_CLASSNAME, &wndcls)))
	{
		// otherwise we need to register a new class
		wndcls.style			= CS_SAVEBITS;
		wndcls.lpfnWndProc		= ::DefWindowProc;
		wndcls.cbClsExtra		= wndcls.cbWndExtra = 0;
		wndcls.hInstance		= hInst;
		wndcls.hIcon			= NULL;
		wndcls.hCursor			= LoadCursor( hInst, IDC_ARROW );
		wndcls.hbrBackground	= (HBRUSH)(COLOR_INFOBK + 1); 
		wndcls.lpszMenuName		= NULL;
		wndcls.lpszClassName	= TITLETIP_CLASSNAME;

		if (!AfxRegisterClass(&wndcls))
			AfxThrowResourceException();
	}
}

CTitleTip::~CTitleTip()
{
    if (::IsWindow(m_hWnd))
        DestroyWindow();
}


BEGIN_MESSAGE_MAP(CTitleTip, CWnd)
	//{{AFX_MSG_MAP(CTitleTip)
	ON_WM_MOUSEMOVE()
	ON_WM_PAINT()
	ON_WM_SYSKEYDOWN()
	ON_WM_KEYDOWN()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


/////////////////////////////////////////////////////////////////////////////
// CTitleTip message handlers

BOOL CTitleTip::Create(CWnd * pParentWnd)
{
	ASSERT_VALID(pParentWnd);

	DWORD dwStyle = WS_BORDER | WS_POPUP; 
	DWORD dwExStyle = WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
	m_pParentWnd = pParentWnd;

	return CreateEx(dwExStyle, TITLETIP_CLASSNAME, NULL, dwStyle, 
					CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
					NULL, NULL, NULL );
}


// Show 		 - Show the titletip if needed
// rectTitle	 - The rectangle within which the original 
//					title is constrained - in client coordinates
// lpszTitleText - The text to be displayed
// xoffset		 - Number of pixel that the text is offset from
//				   left border of the cell
void CTitleTip::Show(CRect rectTitle, LPCTSTR lpszTitleText, 
                     int xoffset /*=0*/, int nMaxChars /*=-1*/, 
					 LPRECT lpHoverRect /*=NULL*/,
					 LPLOGFONT lpLogFont /*=NULL*/,
                     DWORD dwFormat /*=...*/)
{
	ASSERT( ::IsWindow( GetSafeHwnd() ) );

	if (rectTitle.IsRectEmpty())
		return;

	// If titletip is already displayed, don't do anything.
	if( IsWindowVisible() ) 
		return;

	m_rectHover = (lpHoverRect != NULL)? lpHoverRect : rectTitle;
	m_rectHover.right++; m_rectHover.bottom++;

	m_pParentWnd->ClientToScreen( m_rectHover );
	ScreenToClient( m_rectHover );


	// Do not display the titletip is app does not have focus
	if( GetFocus() == NULL )
		return;

	// Define the rectangle outside which the titletip will be hidden.
	// We add a buffer of one pixel around the rectangle
	m_rectTitle.top	= -1;
	m_rectTitle.left   = -xoffset-1;
	m_rectTitle.right  = rectTitle.Width()-xoffset;
	m_rectTitle.bottom = rectTitle.Height()+1;

	// Determine the width of the text
	m_pParentWnd->ClientToScreen( rectTitle );

	CClientDC dc(this);
	m_strTitle = _T("");
	//m_strTitle += _T(" ");
	m_strTitle += lpszTitleText; 
	//m_strTitle += _T(" ");

	CFont font, *pOldFont = NULL;
	if (lpLogFont)
	{
		font.CreateFontIndirect(lpLogFont);
		pOldFont = dc.SelectObject( &font );
	}
	else
	{
		// use same font as ctrl
		pOldFont = dc.SelectObject( m_pParentWnd->GetFont() );
	}

	CSize size = dc.GetTextExtent( m_strTitle );

	TEXTMETRIC tm;
	dc.GetTextMetrics(&tm);
	size.cx += tm.tmOverhang;

	dc.SelectObject( pOldFont );

	m_rectDisplay = rectTitle;
	m_rectDisplay.left += xoffset-1;
    m_rectDisplay.top  += 0;
	m_rectDisplay.right = m_rectDisplay.left + size.cx + xoffset;
    m_rectDisplay.bottom = m_rectDisplay.top + size.cy;
	
	// Do not display if the text fits within available space
	if ( m_rectDisplay.right <= rectTitle.right-xoffset )
        return;

	// We will use avg char width to set max tooltip width
    int nMaxTooltipWidth = -1;
    if (nMaxChars > 0)
    {
	    int nMaxTooltipWidth = (tm.tmAveCharWidth * nMaxChars);
		if (nMaxTooltipWidth < 0)
			nMaxTooltipWidth *= -1;

	    // Rect display to be set to max chars
	    if (m_rectDisplay.Width() > nMaxTooltipWidth)
            m_rectDisplay.right = m_rectDisplay.left + nMaxTooltipWidth;
	}

    //***************************************************************************************
    //Adjust the dimensions of the rect to fit within the client

    // Get the coordinates of the parents client area. (In this case the ListView's client
    // area) and convert coordinates to those of the tooltip.
    CRect rectClient;
    m_pParentWnd->GetClientRect( rectClient );
    m_pParentWnd->ClientToScreen( rectClient );


	// ------------------------------------------------------------------------------
	// Use the screen's right edge as the right hand border, not the right edge of the client.
	// You can comment this out to use the right client as the border.
	CWindowDC wdc(NULL);
	rectClient.right = GetDeviceCaps(wdc, HORZRES) - 8;
	rectClient.bottom = GetDeviceCaps(wdc, VERTRES) - 8;
	//---------------------------------------------------------------------------------------


    //If the right edge exceeds the right edge of the client:
    //          see how much room there is to move the display to the left and adjust the
    //          rectangle that far to the left. If the rect still exceeds the right edge, clip
    //          the right edge to match the client right edge.
    //
    // Does the right display edge exceed the right client edge?
    if (m_rectDisplay.right > rectClient.right)
    {
        // establish what is available left shift wise and what is needed
        int nAvail = 0;
        int nNeeded = m_rectDisplay.right - rectClient.right;

        if (m_rectDisplay.left > rectClient.left)
            nAvail = m_rectDisplay.left - rectClient.left;

        // is there room to move left?
        if (nAvail >= nNeeded)
        {
            m_rectDisplay.OffsetRect(-nNeeded,0);  // yes, move all that is needed
            // increase the size of the window that will be inspected to see if the 
            // cursor has gone outside of the tooltip area by the number of units we
            // offset our display rect.
            m_rectTitle.right += nNeeded;
        }
        else
        {
            m_rectDisplay.OffsetRect(-nAvail,0);   // no, at least move to left edge of client
            // increase the size of the window that will be inspected to see if the 
            // cursor has gone outside of the tooltip area by the number of units we
            // offset our display rect.
            m_rectTitle.right += nAvail;
        }

        // Did we move enough? If not, clip right edge to match client right edge
        if (m_rectDisplay.right > rectClient.right)
            m_rectDisplay.right = rectClient.right;
    }


    //If the left edge exceeds the left edge of the client:
    //          see how much room there is to move the display to the right and adjust the
    //          rectangle that far to the right. If the rect still exceeds the left edge, clip
    //          the left edge to match the client left edge.
    //
    // Does the left display edge exceed the left client edge?
    if (m_rectDisplay.left < rectClient.left)
    {
        // establish what is available right shift wise and what is needed
        int nAvail = 0;
        int nNeeded = rectClient.left - m_rectDisplay.left;

        if (m_rectDisplay.right < rectClient.right)
            nAvail = rectClient.right - m_rectDisplay.right;

        // is there room to move left?
        if (nAvail >= nNeeded)
        {
            m_rectDisplay.OffsetRect(+nNeeded,0);  // yes, move all that is needed
            // increase the size of the window that will be inspected to see if the 
            // cursor has gone outside of the tooltip area by the number of units we
            // offset our display rect.
            m_rectTitle.left -= nNeeded;
        }
        else
        {
            m_rectDisplay.OffsetRect(+nAvail,0);   // no, at least move to left edge of client
            // increase the size of the window that will be inspected to see if the 
            // cursor has gone outside of the tooltip area by the number of units we
            // offset our display rect.
            m_rectTitle.left -= nAvail;
        }

        // Did we move enough? If not, clip left edge to match client left edge
        if (m_rectDisplay.left < rectClient.left)
            m_rectDisplay.left = rectClient.left;        
    }


	// if the calculated width > maxwidth set above then truncate 
    if (nMaxTooltipWidth > 0 && m_rectDisplay.Width() > nMaxTooltipWidth)
        m_rectDisplay.right = m_rectDisplay.left + nMaxTooltipWidth;

    //***************************************************************************************
   
    // Use a "work" rect to calculate the bottom. This work rect will be inset 
    // slightly from the rect we have just created so the tooltip does not touch
    // the sides.
    CRect rectCalc = m_rectDisplay;

    // rectCalc.top += 1;

    int nHeight = dc.DrawText(m_strTitle, rectCalc, dwFormat | DT_CALCRECT);
    m_dwFormat = dwFormat;
    
	// If this is a single line, shorten the display to get rid of any excess blank space
	if (nHeight == tm.tmHeight)
    {
		rectCalc.right = rectCalc.left + size.cx + 3;
    }


    m_rectDisplay.bottom = m_rectDisplay.top + nHeight;

	// ensure the tooltip does not exceed the bottom of the screen
	if (m_rectDisplay.bottom > rectClient.bottom)
    {
		m_rectDisplay.bottom = rectClient.bottom;
        rectCalc.bottom = rectClient.bottom; 
    }

	SetWindowPos( &wndTop, 
        m_rectDisplay.left, 
        m_rectDisplay.top, 
		m_rectDisplay.Width(), 
        m_rectDisplay.Height(),
		SWP_SHOWWINDOW|SWP_NOACTIVATE );
    SetCapture();
}

void CTitleTip::Hide()
{
  	if (!::IsWindow(GetSafeHwnd()))
		return;

	if (GetCapture()->GetSafeHwnd() == GetSafeHwnd())
		ReleaseCapture();

	ShowWindow( SW_HIDE );
}

void CTitleTip::OnMouseMove(UINT nFlags, CPoint point) 
{
	if (!m_rectHover.PtInRect(point)) 
	{
		Hide();
		
		// Forward the message
		ClientToScreen( &point );
		CWnd *pWnd = WindowFromPoint( point );
		if ( pWnd == this ) 
			pWnd = m_pParentWnd;
		
		int hittest = (int)pWnd->SendMessage(WM_NCHITTEST,0,MAKELONG(point.x,point.y));
		
		if (hittest == HTCLIENT)
        {
			pWnd->ScreenToClient( &point );
			pWnd->PostMessage( WM_MOUSEMOVE, nFlags, MAKELONG(point.x,point.y) );
		} 
        else 
			pWnd->PostMessage( WM_NCMOUSEMOVE, hittest, MAKELONG(point.x,point.y) );
	}
}

BOOL CTitleTip::PreTranslateMessage(MSG* pMsg) 
{
	CWnd *pWnd;
	int hittest;
	switch (pMsg->message)
	{
	case WM_LBUTTONDOWN:
	case WM_RBUTTONDOWN:
	case WM_MBUTTONDOWN:
		POINTS pts = MAKEPOINTS( pMsg->lParam );
		POINT  point;
		point.x = pts.x;
		point.y = pts.y;
		ClientToScreen( &point );
		pWnd = WindowFromPoint( point );
		if( pWnd == this ) 
			pWnd = m_pParentWnd;

		hittest = (int)pWnd->SendMessage(WM_NCHITTEST,0,MAKELONG(point.x,point.y));

		if (hittest == HTCLIENT)
		{
			pWnd->ScreenToClient( &point );
			pMsg->lParam = MAKELONG(point.x,point.y);
		} 
		else 
		{
			switch (pMsg->message) 
			{
				case WM_LBUTTONDOWN: 
					pMsg->message = WM_NCLBUTTONDOWN;
					break;
				case WM_RBUTTONDOWN: 
					pMsg->message = WM_NCRBUTTONDOWN;
					break;
				case WM_MBUTTONDOWN: 
					pMsg->message = WM_NCMBUTTONDOWN;
					break;
			}
			pMsg->wParam = hittest;
			pMsg->lParam = MAKELONG(point.x,point.y);
		}

		Hide();
		pWnd->PostMessage(pMsg->message,pMsg->wParam,pMsg->lParam);
		return TRUE;	
		
	case WM_KEYDOWN:
	case WM_SYSKEYDOWN:
		Hide();
		m_pParentWnd->PostMessage( pMsg->message, pMsg->wParam, pMsg->lParam );
		return TRUE;
	}

	if( GetFocus() == NULL )
	{
		Hide();
		return TRUE;
	}

	return CWnd::PreTranslateMessage(pMsg);
}

void CTitleTip::OnPaint() 
{
	CPaintDC dc(this); // device context for painting

    TEXTMETRIC tm;
	dc.GetTextMetrics(&tm);

	CFont *pFont = m_pParentWnd->GetFont(); 	// use same font as ctrl
	CFont *pFontDC = dc.SelectObject( pFont );
	int nHeight=0;

	CRect rect = m_rectDisplay;
	ScreenToClient(rect);

	dc.SetBkMode( TRANSPARENT ); 

	nHeight = dc.DrawText(m_strTitle, rect, m_dwFormat);

	dc.SelectObject( pFontDC );

	// Do not call CWnd::OnPaint() for painting messages
}

void CTitleTip::OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
    Hide();	
	CWnd::OnSysKeyDown(nChar, nRepCnt, nFlags);
}

void CTitleTip::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
    Hide();	
	CWnd::OnKeyDown(nChar, nRepCnt, nFlags);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Nick Hodapp
Web Developer
United States United States
No Biography provided

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 18 Sep 2000
Article Copyright 2000 by Nick Hodapp
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid