Click here to Skip to main content
15,881,882 members
Articles / Desktop Programming / MFC

Hyperlink Scroller

Rate me:
Please Sign up or sign in to vote.
4.82/5 (15 votes)
21 Nov 20022 min read 277K   3.9K   42  
An article on scrolling your hyperlinks, something like a news ticker
// ScrollLink.cpp : implementation file
//

#include "stdafx.h"
#include "ScrollLink.h"

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

/////////////////////////////////////////////////////////////////////////////
// Constant definitions

#define CLR_RED RGB(255, 0, 0) // Red color
#define CLR_BLUE RGB(0, 0, 255) // Blue color
#define CLR_BLACK RGB(0, 0, 0) // Black color
#define CLR_GRAY RGB(178, 178, 178) // Gray color
#define CLR_WHITE RGB(255, 255, 255) // White color

#define SCROLLRATE 1 // Frequency of timer
#define SCROLLSPEED 1 // Amount of pixels to scroll
#define SCROLLALLOWANCE 50 // Amount of pixels between 2 scroll text

#define ID_SCROLLTIMER (WM_USER + 1001) // ID for scroll timer

/////////////////////////////////////////////////////////////////////////////
// CScrollLink

CScrollLink::CScrollLink()
{
	// Empty array
	m_oTextFrames.RemoveAll();
	m_iTextFramesScrolling.RemoveAll();

	// Scroll rate is the value used in SetTimer(), which scrolls your hyperlinks to
	// the left by x pixels, where x is the scroll speed value. Scroll allowance is the
	// gap in pixels between 2 scrolling hyperlinks
	m_uiScrollRate = SCROLLRATE;
	m_uiScrollSpeed = SCROLLSPEED;
	m_uiScrollAllowance = SCROLLALLOWANCE;

	// The text of the hyperlink will be black if there is no url for it. If not, it will
	// be blue. The color of the hyperlink is set to red when mouse it is hovered and the
	// background color of the control is set to the same as the button color.
	m_clrText = CLR_BLACK;
	m_clrHover = CLR_RED;
	m_clrBackground = GetSysColor(COLOR_BTNFACE);

	// Set flag
	m_bOnHover = FALSE;
}

CScrollLink::~CScrollLink()
{
	// Delete CTextFrame objects
	DeleteTextFrames();
}


BEGIN_MESSAGE_MAP(CScrollLink, CStatic)
	//{{AFX_MSG_MAP(CScrollLink)
	ON_WM_ERASEBKGND()
	ON_WM_PAINT()
	ON_WM_TIMER()
	ON_WM_MOUSEMOVE()
	ON_WM_SIZE()
	ON_CONTROL_REFLECT(BN_CLICKED, OnClicked)
	//}}AFX_MSG_MAP
	ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// Create CFont objects for text

void CScrollLink::CreateTextFonts(void)
{
	// Get window font
	CFont* pFont = GetFont();
	
	// Create LOGFONT structure
	LOGFONT lfLogFont;

	// Get LOGFONT structure of current font
	pFont->GetLogFont(&lfLogFont);

	// Set font to be bold
	lfLogFont.lfWeight = FW_BOLD;

	// Create normal font that is bold (when not hovered)
	m_oNormalFont.CreateFontIndirect(&lfLogFont);
	
	// Set underline attribute
	lfLogFont.lfUnderline = TRUE;

	// Create current font with underline attribute (when hovered)
	m_oUnderlinedFont.CreateFontIndirect(&lfLogFont);
}

/////////////////////////////////////////////////////////////////////////////
// Delete CTextFrame objects for scroll text

void CScrollLink::DeleteTextFrames(void)
{
	// Loop through every object pointers in array
	for (int i = 0; i < m_oTextFrames.GetSize(); i ++)
	{
		// Check if pointer is valid
		if (m_oTextFrames.GetAt(i) != NULL)
		{
			// Free memory of object
			delete m_oTextFrames.GetAt(i);

			// Assign null pointer value
			m_oTextFrames.SetAt(i, NULL);
		}
	}

	// Empty array
	m_oTextFrames.RemoveAll();
	m_iTextFramesScrolling.RemoveAll();
}

/////////////////////////////////////////////////////////////////////////////
// Calculate window and text area coordinates

void CScrollLink::CalculateWindowAndTextArea(void)
{
	// Get window coordinates of control
	GetWindowRect(&m_oWindowArea);

	// Convert coordinates with respect to screen
	ScreenToClient(&m_oWindowArea);

	// Duplicate window area for calculation purposes
	m_oTextArea = m_oWindowArea;

	// Deflate area by 2 pixels
	m_oTextArea.DeflateRect(2, 2);
}

/////////////////////////////////////////////////////////////////////////////
// Initialize size of text and its starting position

void CScrollLink::PrepareTextFramesForScrolling(void)
{
	// Create a CClientDC object
	CClientDC oClientDC(this);

	// Select our created font into device context
	CFont* pOldFont = oClientDC.SelectObject(&m_oNormalFont);

	// Loop through every object pointers in array
	for (int i = 0; i < m_oTextFrames.GetSize(); i ++)
	{
		// Get pointer to current looping object
		CTextFrame* pTextFrame = (CTextFrame*)m_oTextFrames.GetAt(i);

		// Empty array
		pTextFrame->m_iXOffSets.RemoveAll();

		// Calculate and set size of text to be drawn based on current device context
		pTextFrame->m_szTextSize = oClientDC.GetTextExtent(pTextFrame->m_strText);
	}

	// Restore original font
	oClientDC.SelectObject(pOldFont);

	// Check if there are at least 1 text frame in array
	if (m_oTextFrames.GetSize() > 0)
	{
		// Set starting position in pixels for first frame to start ball rolling
		((CTextFrame*)m_oTextFrames.GetAt(0))->m_iXOffSets.Add(m_oTextArea.right);
	}
}

/////////////////////////////////////////////////////////////////////////////
// Set new position for current scrolling texts based on scroll speed

void CScrollLink::IncrementScrollingTextFramesPosition(void)
{
	// Make sure there are text to scroll
	if (m_oTextFrames.GetSize() > 0)
	{
		// Loop through the indexes of current scrolling text
		for (int i = 0; i < m_iTextFramesScrolling.GetSize(); i ++)
		{
			// Get index of scrolling text frame object
			int iIndex = m_iTextFramesScrolling.GetAt(i);

			// Get pointer to the text frame object
			CTextFrame* pTextFrame = (CTextFrame*)m_oTextFrames.GetAt(iIndex);

			// Check for the number of same element if array until current position, excluding
			int iXOffSetIndex = NumOfSameElement(m_iTextFramesScrolling, i);

			// Get and calcualte new x offset value
			int iNewXOffSet = pTextFrame->m_iXOffSets.GetAt(iXOffSetIndex) - SCROLLSPEED;

			// Increment position of scrolling text
			pTextFrame->m_iXOffSets.SetAt(iXOffSetIndex, iNewXOffSet);
		}
	}
}

/////////////////////////////////////////////////////////////////////////////
// Set the clipping region so that text not within it will not be drawn 

void CScrollLink::SetClippingRegion(CDC* pDC /* Pointer to CDC object */)
{
	// Create CRgn object
	CRgn oClipRgn;

	// Create clip region for text area
	oClipRgn.CreateRectRgnIndirect(&m_oTextArea);

	// Set clip region
	pDC->SelectClipRgn(&oClipRgn);
}

/////////////////////////////////////////////////////////////////////////////
// Draw scrolling text into text area

void CScrollLink::DrawScrollingText(CDC* pDC /* Pointer to CDC object */)
{
	// Make sure pointer is valid
	if (pDC != NULL)
	{
		// Loop through the indexes of text frame objects to draw
		for (int i = 0; i < m_iTextFramesScrolling.GetSize(); i ++)
		{
			// Get index of scrolling text frame object
			int iIndex = m_iTextFramesScrolling.GetAt(i);

			// Get pointer to the text frame object
			CTextFrame* pTextFrame = (CTextFrame*)m_oTextFrames.GetAt(iIndex);

			// Check for the number of same element if array until current position, excluding
			int iXOffSetIndex = NumOfSameElement(m_iTextFramesScrolling, i);

			// Calculate a rectangular coordinates to draw scroll text
			CRect oDrawingArea(pTextFrame->m_iXOffSets.GetAt(iXOffSetIndex), m_oTextArea.top, m_oTextArea.right, m_oTextArea.bottom);

			// Check if text has no associate url, then draw it as black
			if (pTextFrame->m_strURL.IsEmpty() != FALSE)
			{
				// Set black color text
				pDC->SetTextColor(CLR_BLACK);
			}

			// Text has an associate url, so draw as blue
			else
			{
				// Set blue color text
				pDC->SetTextColor(CLR_BLUE);
			}

			// Draw scroll text
			pDC->DrawText(pTextFrame->m_strText, &oDrawingArea, DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX);

			// Calculate a rectangular coordinates to invalidate previous drawn text
			CRect oInvalidateArea(pTextFrame->m_iXOffSets.GetAt(iXOffSetIndex) + pTextFrame->m_szTextSize.cx, m_oTextArea.top,
								  pTextFrame->m_iXOffSets.GetAt(iXOffSetIndex) + pTextFrame->m_szTextSize.cx + SCROLLSPEED, m_oTextArea.bottom);

			// Repaint last drawn area
			InvalidateRect(&oInvalidateArea);
		}
	}
}

/////////////////////////////////////////////////////////////////////////////
// Add index of new scrolling text if allowance satisfied

void CScrollLink::AddOnNewScrollingText(void)
{
	// Make sure there are text to scroll and there must be at least
	// one text scrolling
	if (m_oTextFrames.GetSize() > 0 && m_iTextFramesScrolling.GetSize() > 0)
	{
		// Get index of last scrolling text frame object index
		int iLastScrolling = m_iTextFramesScrolling.GetSize() - 1;

		// Get index of last scrolling text frame object
		int iIndex = m_iTextFramesScrolling.GetAt(iLastScrolling);
	
		// Get pointer to the text frame object
		CTextFrame* pTextFrame = (CTextFrame*)m_oTextFrames.GetAt(iIndex);

		// Check if there is enough room to scroll another text behind it
		if ((pTextFrame->m_iXOffSets.GetAt(pTextFrame->m_iXOffSets.GetSize() - 1) + pTextFrame->m_szTextSize.cx + SCROLLALLOWANCE) < m_oTextArea.right)
		{
			// Set new index for next scrolling text frame object in array
			iIndex ++;

			// Check if index is not within array size
			if (iIndex >= m_oTextFrames.GetSize())
			{
				// Reset index to start
				iIndex = 0;
			}

			// Get pointer to the next text frame object
			CTextFrame* pTextFrame = (CTextFrame*)m_oTextFrames.GetAt(iIndex);

			// Set starting position in pixels for scrolling
			pTextFrame->m_iXOffSets.Add(m_oTextArea.right);

			// Add new text frame index into array
			m_iTextFramesScrolling.Add(iIndex);
		}
	}
}

/////////////////////////////////////////////////////////////////////////////
// Remove index of finished scrolling text if it runs out of view

void CScrollLink::RemoveFinishedScrollingText(void)
{
	// Make sure there are text to scroll and indexes of text frame objects are there
	if (m_oTextFrames.GetSize() > 0 && m_iTextFramesScrolling.GetSize() > 0)
	{
		// Get index of first scrolling text frame object
		int iIndex = m_iTextFramesScrolling.GetAt(0);

		// Get pointer to the text frame object
		CTextFrame* pTextFrame = (CTextFrame*)m_oTextFrames.GetAt(iIndex);

		// Check if the first text frame has finished scrolling
		if ((pTextFrame->m_iXOffSets.GetAt(0) + pTextFrame->m_szTextSize.cx) <= m_oTextArea.left)
		{
			// Remove x offset from array
			pTextFrame->m_iXOffSets.RemoveAt(0);

			// Remove its index from array
			m_iTextFramesScrolling.RemoveAt(0);
		}
	}
}

/////////////////////////////////////////////////////////////////////////////
// Check the number of same element previously in array

int CScrollLink::NumOfSameElement(CArray<int, int>& iArray /* Reference to array */,
								  int iIndexToCheck /* Index of array item to be check against */)
{
	// Counter to keep track of number of same element
	int iCount = 0;

	// Get value of array content at index position
	int iValue = iArray.GetAt(iIndexToCheck);

	// Loop through every element in array until indexed position (excluding)
	for (int i = 0; i < iIndexToCheck; i ++)
	{
		// Check if element match the value to check
		if (iArray.GetAt(i) == iValue)
		{
			// Increment counter
			iCount ++;
		}
	}

	// Return counter value
	return iCount;
}

/////////////////////////////////////////////////////////////////////////////
// Get index of CTextFrame object in array that is being hovered

int CScrollLink::IsTextBeingHovered(CPoint oCursorPos /* CPoint object containing coordinates of cursor */,
									int* ipXOffSet /* X coordinate of the text frame that is being hovered */)
{
	// Make sure pointer is valid
	if (ipXOffSet != NULL)
	{
		// Loop through every text frame indexes in array
		for (int i = 0; i < m_iTextFramesScrolling.GetSize(); i ++)
		{
			// Get index of scrolling text frame object
			int iIndex = m_iTextFramesScrolling.GetAt(i);

			// Get pointer to the text frame object
			CTextFrame* pTextFrame = (CTextFrame*)m_oTextFrames.GetAt(iIndex);

			for (int x = 0; x < pTextFrame->m_iXOffSets.GetSize(); x ++)
			{
				// Calculate left and right x coordinate of text
				int iLeftXOffSet = pTextFrame->m_iXOffSets.GetAt(x);
				int iRightXOffSet = pTextFrame->m_iXOffSets.GetAt(x) + pTextFrame->m_szTextSize.cx;

				// Calculate starting y coordinate of scroll text
				int iYPos = (m_oTextArea.Height() - pTextFrame->m_szTextSize.cy) / 2;

				// Check if text is left partially out of area
				if (iLeftXOffSet < m_oTextArea.left)
				{
					// Set left x offset value for later formation of rect coordinates
					iLeftXOffSet = m_oTextArea.left;
				}

				// Check if text is right partially out of area
				if (iRightXOffSet > m_oTextArea.right)
				{
					// Set right x offset value for later formation of rect coordinates
					iRightXOffSet = m_oTextArea.right;
				}

				// Calculate a rectangular coordinates to represent the bounding rect around scroll text
				CRect oScrollTextArea(iLeftXOffSet, iYPos, iRightXOffSet, iYPos + pTextFrame->m_szTextSize.cy);

				// Check if cursor over text
				if (oScrollTextArea.PtInRect(oCursorPos) != FALSE)
				{
					// Set x coordinate of text frame
					*ipXOffSet = pTextFrame->m_iXOffSets.GetAt(x);

					// Return index of text frame object in array
					return iIndex;
				}
			}
		}
	}

	// Cursor not on scroll text
	return -1;
}

/////////////////////////////////////////////////////////////////////////////
// Start scrolling text

BOOL CScrollLink::StartScrolling(BOOL bRestart /* Flag to indicate whether to restart scrolling */)
{
	// Make sure there are text to scroll
	if (m_oTextFrames.GetSize() > 0)
	{
		// Stop timer if any
		KillTimer(ID_SCROLLTIMER);

		// Check if scrolling is to be restarted
		if (bRestart == TRUE)
		{
			// Empty array of indexes of current scrolling text frames
			m_iTextFramesScrolling.RemoveAll();

			// Store index of the first text frame to be scrolling
			m_iTextFramesScrolling.Add(0);

			// Initialize size of text and its starting position
			PrepareTextFramesForScrolling();

			// Clear any previous scroll text
			Invalidate();
		}

		// Scrolling is to be continued
		else
		{
		}

		// Start timer
		SetTimer(ID_SCROLLTIMER, SCROLLRATE, NULL);

		// Indicate success
		return TRUE;
	}

	// Indicate failure
	return FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Add scrolling text and its url

BOOL CScrollLink::AddScrollText(CString strText /* Text to scroll */,
								CString strURL /* URL behind scrolled text */)
{
	// Allocate memory for CTextFrame object
	CTextFrame* pTextFrame = new CTextFrame(strText, strURL);

	// Check if memory allocation successful
	if (pTextFrame != NULL)
	{
		// Store object into array
		m_oTextFrames.Add(pTextFrame);

		// Assign null pointer value
		pTextFrame = NULL;

		// Indicate success
		return TRUE;
	}

	// Indicate failure
	return FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Remove all previously added scroll text

BOOL CScrollLink::RemoveAllScrollText(void)
{
	// Remove all scroll text
	DeleteTextFrames();

	// Repaint
	Invalidate();

	// Indicate success
	return TRUE;
}

/////////////////////////////////////////////////////////////////////////////
// CScrollLink message handlers

void CScrollLink::PreSubclassWindow() 
{
	// Create default font for text
	CreateTextFonts();

	// Calculate window and text area coordinates
	CalculateWindowAndTextArea();

	CStatic::PreSubclassWindow();
}

BOOL CScrollLink::OnEraseBkgnd(CDC* pDC) 
{
	// Paint background with color
	pDC->FillSolidRect(&m_oWindowArea, m_clrBackground);
	
	// Background erased
	return TRUE;
}

void CScrollLink::OnPaint() 
{
	CPaintDC dc(this); // device context for painting
	
	// Duplicate window area for calculation purposes
	CRect oInnerArea = m_oWindowArea;
	
	// Deflate rect area by 1 pixel
	oInnerArea.DeflateRect(1, 1);

	// Draw 2 3d borders
	dc.Draw3dRect(&oInnerArea, GetSysColor(COLOR_BTNHILIGHT), GetSysColor(COLOR_BTNSHADOW));
	dc.Draw3dRect(&m_oWindowArea, GetSysColor(COLOR_BTNSHADOW), GetSysColor(COLOR_BTNHILIGHT));

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

void CScrollLink::OnTimer(UINT nIDEvent) 
{
	// Set new position for current scrolling texts based on scroll speed
	IncrementScrollingTextFramesPosition();

	// Create a CClientDC object
	CClientDC oClientDC(this);

	// Set the clipping region so that text not within it will not be drawn 
	SetClippingRegion(&oClientDC);
	
	// Select font for text to be drawn
	CFont* pOldFont = oClientDC.SelectObject(&m_oNormalFont);

	// Set background mode to opaque
    oClientDC.SetBkMode(OPAQUE);

	// Set text background color to match control background
	oClientDC.SetBkColor(m_clrBackground);

	// Draw scrolling text into text area
	DrawScrollingText(&oClientDC);

	// Restore original font
	oClientDC.SelectObject(pOldFont);

	// Remove index of finished scrolling text if it runs out of view
	RemoveFinishedScrollingText();

	// Add index of new scrolling text if allowance satisfied
	AddOnNewScrollingText();

	CStatic::OnTimer(nIDEvent);
}

void CScrollLink::OnMouseMove(UINT nFlags, CPoint point) 
{
	// X coordinate of hovered text frame
	int iXOffSet = 0;

	// Device context for client area
	CClientDC oClientDC(this);

	// Get index of text frame object that is being hovered
	m_iHoveredTextFrame = IsTextBeingHovered(point, &iXOffSet);

	// Check if mouse is not hovering text
	if (m_bOnHover == FALSE)
	{
		// Check if text is being hovered
		if (m_iHoveredTextFrame != -1)
		{
			// Stop timer
			KillTimer(ID_SCROLLTIMER);

			// Mouse is now hovering text
			m_bOnHover = TRUE;

			// Create TRACKMOUSEEVENT structure
			TRACKMOUSEEVENT tmeMouseEvent;		

			// Initialize members of structure
			tmeMouseEvent.cbSize = sizeof(TRACKMOUSEEVENT);
			tmeMouseEvent.dwFlags = TME_LEAVE;
			tmeMouseEvent.hwndTrack = m_hWnd;
	
			// Track mouse leave event
			_TrackMouseEvent(&tmeMouseEvent);

			// Get pointer to the text frame object
			CTextFrame* pTextFrame = (CTextFrame*)m_oTextFrames.GetAt(m_iHoveredTextFrame);

			// Check if this text has a hyperlink
			if (pTextFrame->m_strURL.IsEmpty() == FALSE)
			{
				// Set the clipping region so that text not within it will not be drawn 
				SetClippingRegion(&oClientDC);
	
				// Select font for text to be drawn
				CFont* pOldFont = oClientDC.SelectObject(&m_oUnderlinedFont);

				// Set background mode to opaque
				oClientDC.SetBkMode(OPAQUE);

				// Set text color
				oClientDC.SetTextColor(m_clrHover);

				// Set text background color to match control background
				oClientDC.SetBkColor(m_clrBackground);

				// Calculate a rectangular coordinates to draw scroll text
				CRect oDrawingArea(iXOffSet, m_oTextArea.top, m_oTextArea.right, m_oTextArea.bottom);

				// Draw scroll text
				oClientDC.DrawText(pTextFrame->m_strText, &oDrawingArea, DT_LEFT | DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX);

				// Restore original font
				oClientDC.SelectObject(pOldFont);
			}
		}
	}
	
	// Mouse move when text is hovered
	else
	{
		// Check if text is not being hovered
		if (m_iHoveredTextFrame == -1)
		{
			// Mouse not hovering
			OnMouseLeave(0, 0);
		}
	}
	
	CStatic::OnMouseMove(nFlags, point);
}

LRESULT CScrollLink::OnMouseLeave(WPARAM wParam, LPARAM lParam)
{
	// Mouse not hovering text
	m_bOnHover = FALSE;

	// Continue scrolling text
	SetTimer(ID_SCROLLTIMER, m_uiScrollRate, NULL);	

	return NULL;
}

void CScrollLink::OnSize(UINT nType, int cx, int cy) 
{
	CStatic::OnSize(nType, cx, cy);
	
	// Calculate window and text area coordinates
	CalculateWindowAndTextArea();
}

BOOL CScrollLink::DestroyWindow() 
{
	// Kill timer
	KillTimer(ID_SCROLLTIMER);
	
	return CStatic::DestroyWindow();
}

void CScrollLink::OnClicked() 
{
	// Check if mouse hovering text and index of text being hovered is valid
	if (m_bOnHover != FALSE && m_iHoveredTextFrame != -1)
	{
		// Get pointer to the text frame object
		CTextFrame* pTextFrame = (CTextFrame*)m_oTextFrames.GetAt(m_iHoveredTextFrame);

		// Make sure url is not empty
		if (pTextFrame->m_strURL.IsEmpty() != TRUE)
		{
			// Open browser and go to url
			ShellExecute(NULL, _T("open"), pTextFrame->m_strURL, NULL, NULL, SW_SHOW);
		}
	}	
}

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CTextFrame::CTextFrame()
{
	// Initialize member data
	m_szTextSize.cx = 0;
	m_szTextSize.cy = 0;
	m_strText = _T("");
	m_strURL = _T("");

	// Empty array
	m_iXOffSets.RemoveAll();
}

CTextFrame::CTextFrame(CString strText /* Text of scrol */,
					   CString strURL /* URL behind text */)
{
	// Initialize member data
	m_szTextSize.cx = 0;
	m_szTextSize.cy = 0;
	m_strText = strText;
	m_strURL = strURL;

	// Empty array
	m_iXOffSets.RemoveAll();
}

CTextFrame::~CTextFrame()
{
	// Empty array
	m_iXOffSets.RemoveAll();
}

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


Written By
Singapore Singapore
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions