An improved hyperlink control

WEBINAR: On-demand webcast

How to Boost Database Development Productivity on Linux, Docker, and Kubernetes with Microsoft SQL Server 2017 REGISTER >

The CHyperlink example project dialog  Download Source Code and Demo


Introduction

After having tried some hyperlink controls, I felt the necessity to write my own control that was more flexible and had a more consistent programming interface. My intention was to create an hyperlink that could be used not only with text but also with icons and bitmaps, and that supported dynamic modification of its style and font. Moreover, I wanted that my control had the look and feel of an usual web page hyperlink. Why cannot hyperlinks have the input focus? Why cannot I move the focus from an hyperlink to another using the "tab" key as if they were normal dialog buttons and activate them pressing the spacebar?

Well, here is an hyperlink control that makes all these beautiful things possible. It was realized with the help of some articles written by Paul DiLascia on MSJ and Stuart Patterson on Windows Developer's Journal. It's rightful to tell that I think at my control as an improvement of the homonymous control developed by Chris Maunder, whose work was excellent.

Comparison with existing hyperlinkcontrols

In this section, I summarize the main features that distinguish my control from other similar controls you can find in this site. I hope this will help you to decide which code suits your needs better.

  • You can set the hyperlink on any type of static control (bitmaps and icons above all!).
  • The hyperlink can get the input focus (a special color is used to show the focus state) and can be activated with the spacebar like normal dialog buttons.
  • The hyperlink maintains the correct aspect when you change its style, text or font.
  • Control resizing works correctly even if it has a nonclient border.
  • Simplified programming interface.

Using the control

To use the hyperlink control, first create a static control with your resource editor (text, icon, or bitmap), then give it a control ID (e.g. IDC_HYPERLINK). If you want your control to be able to get the focus, you have to set the "tabstop" style, since for static controls it is not the default. I advice to set it only with text controls, which can change color when focused. Then, attach it to a member variable of type CHyperLink (ClassWizard makes this very simple). Don't forget to include the "HyperLink.h" file in your dialog class implementation file. The member function SetURL() allows to specify the link URL. However, setting an explicit URL is not obligatory for text controls: if you specify a caption text, this is taken as URL. Vice versa, if you don't specify a caption text, but 'manually' set the URL with SetURL(), then the text is set equal to the URL. For all other types of static controls instead, you have the responsibility to set the URL using SetURL() (usually in the OnInitDialog() member function of your dialog class), or otherwise a debug assertion will occur. The default link colors are blue, dark cyan, purple and red, respectively for normal, focused, visited and "cursor over" states.

Customizing the hyperlink

The hyperlink styles are static class constants. They are shown in the following table

Style Meaning Default
StyleUnderline The link is underlined YES
StyleAutoSize The control resizes itself automatically when text or font changes YES
StyleUseHover The link use the "hand over" color NO
StyleDownClick The link is activated when mouse button "goes down" NO
StyleGetFocusOnClick The link gets the input focus when clicked NO
StyleNoHandCursor The link doesn't use the default hand cursor NO
StyleNoActiveColor The link doesn't change color when activated (i.e. when it get the focus) NO
  • To modify link style call the member function ModifyLinkStyle(dwRemove, dwAdd) where dwRemove is a bitwise OR of the styles you want to remove and dwAdd is a bitwise OR of the styles you want to add (strange but true!).

Setting the StyleAutoSize, the control will automatically resize itself to fit the size of the caption (even while it is visible!). The resizing occurs also if you set a new window caption text or font. Remember that this style applies to text controls only. For icons and bitmaps, auto-resizing is a default characteristic, and StyleAutoSize becomes meaningless. The resizing will honour the SS_CENTERIMAGE, SS_LEFT, SS_RIGHT and SS_CENTER static control flags. If you don't want the style change to be dinamically applied to the control, ModifyLinkStyle() has a third optional boolean parameter: just set it to FALSE (the default is TRUE).

  • To mark/unmark a link as visited call the SetVisited() member function.
  • To modify link colors, call the SetColors() member function, in the following way:
    SetColors(crLinkColor, crActiveColor, crVisitedColor, crHoverColor);

The last parameter is optional: if not specified the system default highlight color will be used. Alternatively, fill a HYPERLINKCOLORS structure (a typedef defined in "HyperLink.h") and pass it to the overloaded version of SetColors(). This structure is also used by GetColors() to retrieve the current link colors.
SetColors() is a static member function: the color change has effect on all instances of CHyperLink and you need to redraw your control in order that the new colors to be applied.

  • To set a mouse cursor different from the default hand, call SetLinkCursor(). This function requires a unique argument: the handle of a new cursor. It is a static function and has effect on all hyperlinks, just like SetColors().

All the "set" functions above have their natural "get" counterparts. See the following section.

Operations for CHyperLink:

Here is an excerpt from the class declaration


// Operations
public:	
	static void GetColors(HYPERLINKCOLORS& linkColors);

	static HCURSOR GetLinkCursor();
	static void SetLinkCursor(HCURSOR hCursor);
    
	static void SetColors(COLORREF crLinkColor, COLORREF crActiveColor, 
	   		   COLORREF crVisitedColor, COLORREF crHoverColor = -1);
	static void SetColors(HYPERLINKCOLORS& colors);

	void SetURL(CString strURL);
	CString GetURL() const;

	DWORD GetLinkStyle() const;
	BOOL ModifyLinkStyle(DWORD dwRemove, DWORD dwAdd, BOOL bApply=TRUE);	
    
	void SetWindowText(LPCTSTR lpszText);
	void SetFont(CFont *pFont);
	
	BOOL IsVisited() const;
	void SetVisited(BOOL bVisited = TRUE);
	
	// Use this if you want to subclass and also set different URL
	BOOL SubclassDlgItem(UINT nID, CWnd* pParent, LPCTSTR lpszURL=NULL) {
		m_strURL = lpszURL;
		return CStatic::SubclassDlgItem(nID, pParent);
	}

Implementation details

Some notes about implementation. I used the MFC message reflection to set the text color for the control (see the ON_WM_CTLCOLOR_REFLECT() macro in the message map). Message reflection can be a very complicated argument. Briefly, many controls notifies some event messages to their parent window, so that the latter has a chance to handle them. When the parent window sends back the message to the control we have the so-called message reflection. This mechanism is well implemented in MFC. Reflected messages appear preceded by an equal sign in ClassWizard.

Reading the code, you may be surprised that the control works without the SS_NOTIFY style. Normally, a static control does not get mouse events unless it has the SS_NOTIFY style. However, as Paul DiLascia asserts, handling the WM_NCHITTEST message and making the OnNcHitTest() functions returns the hit-test value HTCLIENT achieves the same effect as SS_NOTIFY, but requires fewer lines of code and is more reliable than turning on SS_NOTIFY in OnCtlColor because Windows doesn't send WM_CTLCOLOR to bitmap static controls.

For dynamic text and font modification I supplied special versions of SetFont() and SetWindowText() functions. These functions perform their operations while the window is kept hidden to maintain the correct link aspect.

Author's note

I'm continuously working to improve this control. I'll be grateful to you if you mail me your comments, advices, or bug apparition reports!.

Latest additions

  • Link styles are now static class constants.
  • Colors and cursor and their relative get/set functions, are now static class members.
  • Many bug fixes in resizing and color management.

What's new in this release

This release was compiled and tested with Visual C++ 5.00. Uwe Kleim reported me a bug in the ModifyLinkStyle() member function and now it has been fixed. Thanks Uwe!

Last updated: 11 November 1998



Comments

  • onMouseMove bug

    Posted by prakashp5562 on 12/17/2006 05:07pm

    Change the font of the dialog to Tahoma 8.  Now on Mousemove you find that - the text gets disturbed.
    
    Resolution: 
    Add 	ON_WM_ERASEBKGND() in the message map
     
    BOOL CHyperLink::OnEraseBkgnd(CDC* pDC) 
    {
        CRect rect;
        GetClientRect(rect);
        pDC->FillSolidRect(rect, ::GetSysColor(COLOR_3DFACE));
    
        return TRUE;
    }

    Reply
  • how to add hyper link in menu item

    Posted by Legacy on 06/03/2003 12:00am

    Originally posted by: khalil

    I want to add hyper link in menu item so plz tell me how i can add hyper link in menu item

    Reply
  • Won't work in About-Dialog.

    Posted by Legacy on 09/04/2002 12:00am

    Originally posted by: P

    Hi

    I tried to use this nice control in an aboutdialog - but it won't work !
    Any ideas how to do that ?

    Thanks
    P

    Reply
  • Hyperlink in a textbox?

    Posted by Legacy on 06/26/2002 12:00am

    Originally posted by: WH

    How can i place a hyperlink in a textbox?

    Reply
  • Load IDC_HAND on Win2k & XP

    Posted by Legacy on 01/04/2002 12:00am

    Originally posted by: Thomas Baust

    First, your control is great.
    
    

    But, it is better to load system's IDC_HAND Cursor on Win2k & XP. If the system can't find IDC_HAND (i.e. Win9x), hLinkCursor will NULL, and simply continue with the orginal code. So i have changed SetDefaultCursor:

    void CHyperLink::SetDefaultCursor()
    {
    if (g_hLinkCursor == NULL) // No cursor handle - load
    {
    HCURSOR hHandCursor = ::LoadCursor(NULL,MAKEINTRESOURCE(32649)); // try to load system cursor
    if (hHandCursor)
    g_hLinkCursor = CopyCursor(hHandCursor);

    if (g_hLinkCursor == NULL) // No cursor handle - load our own
    {
    // Get the windows directory
    CString strWndDir;
    GetWindowsDirectory(strWndDir.GetBuffer(MAX_PATH), MAX_PATH);
    strWndDir.ReleaseBuffer();

    strWndDir += _T("\\winhlp32.exe");
    // This retrieves cursor #106 from winhlp32.exe, which is a hand pointer
    HMODULE hModule = LoadLibrary(strWndDir);
    if (hModule) {
    hHandCursor = ::LoadCursor(hModule, MAKEINTRESOURCE(106));
    if (hHandCursor)
    g_hLinkCursor = CopyCursor(hHandCursor);
    }
    FreeLibrary(hModule);
    }
    }
    }

    Reply
  • Error on a DLL

    Posted by Legacy on 08/23/2000 12:00am

    Originally posted by: Rod

    There is an error when I use your code in a DLL.
    
    

    I have to use the line:

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    So the dll can load its resources.

    It works the first time I load the dialog.

    But the second time it crashes here:

    BOOL CGdiObject::Attach(HGDIOBJ hObject)
    {
    ASSERT(m_hObject == NULL); // only attach once, detach on destroy
    if (hObject == NULL)
    return FALSE;
    CHandleMap* pMap = afxMapHGDIOBJ(TRUE); // create map if not exist
    ASSERT(pMap != NULL);
    pMap->SetPermanent(m_hObject = hObject, this);
    return TRUE;
    }

    Exactly at this line:

    ASSERT(m_hObject == NULL); // only attach once, detach on destroy

    Does any one know why? and how to solve it?

    Reply
  • I would like to use your control to hold multi line text in multi colors.

    Posted by Legacy on 11/03/1999 12:00am

    Originally posted by: Phil

    Please forgive me for hacking your code. I love your control but I want it to do two more things.

    1st display multiple lines of text.
    2nd show at least two colors per line.

    I'm a pretty good C programmer, but a little new to C++ and Windows, so I included my code change in the hopes of getting your opinion.

    Secondly, can the control display more than one color? If so could you give me some hints?

    Thanks.


    void CHyperLink::AdjustWindow()
    {
    ...
    // Get the extent of window text
    CString strWndText;
    GetWindowText(strWndText);

    CDC* pDC = GetDC();
    CFont* pOldFont = pDC->SelectObject(&m_Font);
    CSize Extent = pDC->GetTextExtent(strWndText);

    //Code I added starts here.
    //Count the number of line returns
    char* pszLineCount = strWndText.GetBuffer(0);
    short nLineCount = 1;
    while(*pszLineCount != NULL) {
    if(*pszLineCount == '\n')
    nLineCount++;
    pszLineCount++;
    }
    strWndText.ReleaseBuffer();
    //Expand the width by the number of line returns.
    Extent = CSize(Extent.cx , Extent.cy * nLineCount);

    Reply
  • How can we do the same in ATL?

    Posted by Legacy on 07/30/1999 12:00am

    Originally posted by: Gangadhar Bhat

    Hi,

    Your hyperlink control looks great!. It works great too!
    I am doing GUI using ATL so that I can make it snappable to MMC console. Since it is an executable( not DLL), i can not import any MFC classes. My user interface strictly uses ATL and uses IWebbrowser interface to navigate web pages. However, I have hard time to see the samples for doing hyperlink control using strictly win32 so that i can plug in that code in my ATL code. Could please help to find this out. I am kind of novice to user interface area. I used to be driver guy.

    I would greatly appreciate your help.

    Thanks..

    GD

    Reply
  • How can we do the same in ATL?

    Posted by Legacy on 07/30/1999 12:00am

    Originally posted by: Gangadhar Bhat

    Hi,

    Your hyperlink control looks great!. It works great too!
    I am doing GUI using ATL so that I can make it snappable to MMC console. Since it is an executable( not DLL), i can not import any MFC classes. My user interface strictly uses ATL and uses IWebbrowser interface to navigate web pages. However, I have hard time to see the samples for doing hyperlink control using strictly win32 so that i can plug in that code in my ATL code. Could please help to find this out. I am kind of novice to user interface area. I used to be driver guy.

    I would greatly appreciate your help.

    Thanks..

    GD

    Reply
  • Crash - Possible Cause

    Posted by Legacy on 07/04/1999 12:00am

    Originally posted by: Jason Teagle

    I think the crash caused by the ShellExecute() may be because the working directory in the line is specified as NULL, rather than an empty string (""). The help on ShellExecute() makes it clear that the command-line parameter string pointer CAN be NULL, but it does not say this for the working directory. Try changing this.

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • As all sorts of data becomes available for storage, analysis and retrieval - so called 'Big Data' - there are potentially huge benefits, but equally huge challenges...
  • The agile organization needs knowledge to act on, quickly and effectively. Though many organizations are clamouring for "Big Data", not nearly as many know what to do with it...
  • Cloud-based integration solutions can be confusing. Adding to the confusion are the multiple ways IT departments can deliver such integration...

Most Popular Programming Stories

More for Developers

RSS Feeds

Thanks for your registration, follow us on our social networks to keep up-to-date