Click here to Skip to main content
15,886,724 members
Articles / Mobile Apps / Windows Mobile

Using the Grid Control in a Doc/View framework

Rate me:
Please Sign up or sign in to vote.
4.78/5 (25 votes)
29 Aug 2000CPOL 354.4K   4.2K   104  
A simple tutorial that demonstrates how to use the grid control in a doc/view application.
////////////////////////////////////////////////////////////////////////////
// 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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder CodeProject
Canada Canada
Chris Maunder is the co-founder of CodeProject and ContentLab.com, and has been a prominent figure in the software development community for nearly 30 years. Hailing from Australia, Chris has a background in Mathematics, Astrophysics, Environmental Engineering and Defence Research. His programming endeavours span everything from FORTRAN on Super Computers, C++/MFC on Windows, through to to high-load .NET web applications and Python AI applications on everything from macOS to a Raspberry Pi. Chris is a full-stack developer who is as comfortable with SQL as he is with CSS.

In the late 1990s, he and his business partner David Cunningham recognized the need for a platform that would facilitate knowledge-sharing among developers, leading to the establishment of CodeProject.com in 1999. Chris's expertise in programming and his passion for fostering a collaborative environment have played a pivotal role in the success of CodeProject.com. Over the years, the website has grown into a vibrant community where programmers worldwide can connect, exchange ideas, and find solutions to coding challenges. Chris is a prolific contributor to the developer community through his articles and tutorials, and his latest passion project, CodeProject.AI.

In addition to his work with CodeProject.com, Chris co-founded ContentLab and DeveloperMedia, two projects focussed on helping companies make their Software Projects a success. Chris's roles included Product Development, Content Creation, Client Satisfaction and Systems Automation.

Comments and Discussions