Small C++ Class to Transform Any Static Control into a Hyperlink Control

Introduction

This class is small, efficient, and is compatible with Win32 API programs and MFC programs as well.

Prior to developing my own hyperlink control implementation, I have been looking around on the net in search of pre-written hyperlink controls. There are a lot around, but none were good enough for my standards. Some are bloated with features that I don’t need and want. Some are plagued with bugs. Some are simple but lack one or two features I wanted to see. The best that I have found is the excellent code written by Neal Stublen. I have been inspired by the elegance that his solution offers; it can be used with Win32 API programs and MFC programs as well. Unfortunately, it has a bug and misses three features I was looking for. For this reason, I decided to write my own hyperlink control code based on Neal Stublen’s work. In this article, I will mention hyperlink options that Microsoft offers, describe the features Neal did put in his code, the features that I added, the bug fix I propose to Neal’s code and some other improvements that I have added.

Microsoft Options

After the initial release of this article, some readers pointed out that WTL is offering exactly what I was looking for. In retrospect, I still think that it was the right decision to write my own class. One benefit of WTL over my solution is that it is more complete and has more features. However, because a WTL user’s goal is to create small and fast executable files, they would benefit from using my class over the WTL one *if* my class provides all the features they need. In some aspects, WTL implementation has some drawbacks that my class doesn’t have:


  1. It has much more code.

  2. It uses a WTL message dispatching scheme that might be faster and slimmer than the MFC one but it is not as efficient as a pure Windows procedure that this class is using.

  3. It has tooltips, but I didn’t see how you could customize them to send the URL text in the status bar as easily as it is with my class.

  4. You must have WTL in your project to use their hyperlink class. Mine works with a Win32 API C project, an MFC one, and even with WTL.

I have never used WTL to write a program but if I would, I would use the class described in this article. Microsoft proposes another option for people looking for hyperlink controls. It added such a control to comctrl32.dll version 6, but this version is available only on XP and Microsoft states. An application that has to run on other Windows operating systems cannot rely on the new common control library being present and should not use the features that are available only in ComCtl32.dll, version 6. It might pose a problem if you don’t want to restrict your potential user base strictly to this target.

Features

First, the changes needed for a static control to become a hyperlink that Neal addressed are:


  1. Clicking the text needs to open a browser window to the location specified by the text.

  2. The cursor needs to change from the standard arrow cursor to a pointing index finger when it moves over the control.

  3. The text in the control needs to be underlined when the cursor moves over the control.

  4. A hyperlink control needs to display text in a different color—black just won’t do.

The features that I added are:


  1. A hyperlink control once visited needs to change color.

  2. The hyperlink control should be accessible from the keyboard.

  3. It should install some kind of hooks to allow the programmer to perform some actions when the control has the focus or when the cursor is hovering over the control.

Before describing how the new features have been implemented, let me introduce you to the major architectural change that the code underwent. I placed the code into a class. Here is the class definition:


class CHyperLink
{
public:
CHyperLink(void);
virtual ~CHyperLink(void);

BOOL ConvertStaticToHyperlink(HWND hwndCtl, LPCTSTR strURL);
BOOL ConvertStaticToHyperlink(HWND hwndParent,
UINT uiCtlId, LPCTSTR strURL);

BOOL setURL( LPCTSTR strURL);
LPCTSTR getURL(void) const { return m_strURL; }

protected:
/*
* Override if you want to perform some action when
* the link has the focus or when the cursor is over
* the link such as displaying the URL somewhere.
*/
virtual void OnSelect(void) {}
virtual void OnDeselect(void) {}

LPTSTR m_strURL; // hyperlink URL

private:
// Hyperlink colors
static COLORREF g_crLinkColor, g_crVisitedColor;
static HCURSOR g_hLinkCursor; // Cursor for hyperlink
static HFONT g_UnderlineFont; // Font for underline display
static int g_counter; // Global resources user
// counter
BOOL m_bOverControl; // cursor over control?
BOOL m_bVisited; // Has it been visited?
HFONT m_StdFont; // Standard font
WNDPROC m_pfnOrigCtlProc;

void createUnderlineFont(void);
static void createLinkCursor(void);
void createGlobalResources(void)
{
createUnderlineFont();
createLinkCursor();
}
static void destroyGlobalResources(void)
{
/*
* No need to call DestroyCursor() for cursors
* acquired through LoadCursor().
*/
g_hLinkCursor = NULL;
DeleteObject(g_UnderlineFont);
g_UnderlineFont = NULL;
}

void Navigate(void);

static void DrawFocusRect(HWND hwnd);
static LRESULT CALLBACK _HyperlinkParentProc(HWND hwnd,
UINT message, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK _HyperlinkProc(HWND hwnd,
UINT message, WPARAM wParam, LPARAM lParam);
};

The reasons that motivated this change are:


  1. Allow the user to derive a new class to customize the control behavior when a hyperlink is selected or deselected.

  2. Reduce the number of GetProp() calls in the window procedures by fetching the pointer on an object containing all the needed variables with one GetProp() call.

#1 can be achieved by overriding the OnSelect() and OnDeselect() functions. This will be demonstrated later, when I present the demo application.

This brought me to introduce another improvement. Have you noticed that some members are static? This allows multiple hyperlink controls to share the same resources. Shared resources include the hand cursor and the underlined font. This block has been added to the ConvertStaticToHyperlink() function:


if( g_counter++ == 0 )
{
createGlobalResources();
}

And this code has been added to the WM_DESTROY message handler in the control window procedure:


if( –CHyperLink::g_counter <= 0 )
{
destroyGlobalResources();
}

To the first ConvertStaticToHyperlink() call, global resources will be allocated and when the last hyperlink is destroyed, it will destroy the shared resources as well. The advantages to this approach are that it will make memory usage more efficient and the hand cursor will be loaded just once. Here is the new WM_SETCURSOR code:


case WM_SETCURSOR:
{
SetCursor(CHyperLink::g_hLinkCursor);
return TRUE;
}

Now let me get back to the new features. The simplest one is to change the color of the hyperlink when it is visited. A very simple change to the WM_CTLCOLORSTATIC handler is needed. It just checks a boolean variable state that is set to true when the link is visited. Here is the pertinent code:


inline void CHyperLink::Navigate(void)
{
SHELLEXECUTEINFO sei;
::ZeroMemory(&sei,sizeof(SHELLEXECUTEINFO));
sei.cbSize = sizeof( SHELLEXECUTEINFO ); // Set Size
sei.lpVerb = TEXT( “open” ); // Set Verb
sei.lpFile = m_strURL; // Set Target
// To Open
sei.nShow = SW_SHOWNORMAL; // Show Normal

WINXDISPLAY(ShellExecuteEx(&sei));
m_bVisited = TRUE;
}

case WM_CTLCOLORSTATIC:
{
HDC hdc = (HDC) wParam;
HWND hwndCtl = (HWND) lParam;
CHyperLink *pHyperLink =
(CHyperLink *)GetProp(hwndCtl, PROP_OBJECT_PTR);

if(pHyperLink)
{
LRESULT lr = CallWindowProc(pfnOrigProc,
hwnd, message, wParam, lParam);
if (!pHyperLink->m_bVisited)
{
// This is the most common case for static
// branch prediction optimization
SetTextColor(hdc, CHyperLink::g_crLinkColor);
}
else
{
SetTextColor(hdc, CHyperLink::g_crVisitedColor);
}
return lr;
}
break;
}

To support the keyboard, the following messages must be handled:


  1. WM_KEYUP

  2. WM_SETFOCUS

  3. WM_KILLFOCUS

The control will respond to a space key press. WM_SETFOCUS and WM_KILLFOCUS draw the focus rectangle. It is drawn against the parent window. The reason for doing so is because first; otherwise, the focus rectangle will be too close to the hyperlink text. Secondly, I played with making the hyperlink controls transparent by returning a hollow brush from the WM_CTLCOLOR_STATIC handler. When the parent was erasing the control background, it was messing with the focus rectangle. By drawing the focus rectangle against the parent window, it fixes these small problems.

More by Author

Must Read