Click here to Skip to main content
Click here to Skip to main content

Making the CMFCLinkCtrl Class More Developer-Friendly

By , 11 Jan 2011
Rate this:
Please Sign up or sign in to vote.
Screen shot of MFC Hyperlink demo program

Introduction

For the past few years I've been programming almost exclusively in C#, but recently I was asked to put together an application in native C++ that would include hyperlink controls on some of the dialogs. The application was to run on Windows XP or later. Since most of my C++ experience has been with MFC, and since I'm now using Visual Studio 2010, it seemed natural to use the new CMFCLinkCtrl [^] to implement the hyperlinks. This control was introduced as part of the Visual C++ 2008 Feature Pack and then made a regular part of MFC in Visual Studio 2010.

As soon as I started working with CMFCLinkCtrl I realized that its limitations were going to make the default implementation unsatisfactory for my application. Chief among the limitations is the fact that the typeface, font size, and hyperlink text colors are fixed. By "fixed" I mean hard-coded. Well, not quite hard-coded but something pretty close; in CMFCLinkCtrl::OnDraw the hyperlink font is initialized like this:

pDC-></span />SelectObject(&afxGlobalData.fontDefaultGUIUnderline);

In turn, afxGlobalData.fontDefaultGUIUnderline, which is initialized by the MFC framework along with all of the afxGlobalData values when an MFC application launches, is derived from the stock object DEFAULT_GUI_FONT, which generally points to the MS Shell Dlg logical font. I'll have more to say about this later, but the important thing is that this font may not match the rest of your GUI at all. One way to change it is by modifying afxGlobalData.fontDefaultGUIUnderline for your application, and I'll show you how to do that in this article. However, the disadvantage with changing this global value is that it will affect every instance of CMFCLinkCtrl in an identical way throughout your application. In some cases that might be exactly what you want, but generally I prefer the flexibility of being able to specify the font for each hyperlink individually. By that I mean both the typeface and the size, plus any other properties I might want to modify such as whether the hyperlink is underlined and whether it uses italics or bold text. I also like being able to just let a hyperlink automatically use the dialog font, a practice that covers probably three-quarters of the hyperlinks I use in applications. The class derived from CMFCLinkCtrl and provided with this article, which I've cleverly named CHyperlink, does all of that and more.

Background

The CMFCLinkCtrl class, introduced originally with the Visual C++ 2008 Feature Pack [^] and now distributed with Visual Studio 2010, is Microsoft's latest MFC-based hyperlink control. Unlike CLinkCtrl [^], which has been part of MFC since version 7.0 (released with Visual Studio 2002) and is just a thin wrapper on the Win32 SysLink control [^], CMFCLinkCtrl was written explicitly as an MFC hyperlink control. Previously, if you wanted a high-level hyperlink control you had to build your own, and several well-known hyperlink classes have been developed for MFC over the years, including the original CodeProject hyperlink control [^] developed by Chris Maunder and later extended by Giancarlo Iovino at CodeGuru [^] and by Hans Dietrich here at CodeProject [^].

All three of these controls, and several others that were developed around the same time, owe their heritage to the CStaticLink control [^] published by Paul DiLascia in the December 1997 issue of what was then Microsoft Systems Journal (now MSDN Magazine). One exception is Nguyen Duc Thanh's CURLLinkButton class [^], which derives from CButton rather than from CStatic. In a similar way, the new CMFCLinkCtrl class differs from most previous VC++ hyperlink controls in that it inherits from CMFCButton, a derivative of CButton. Like CMFCLinkCtrl, the CMFCButton class was incorporated into MFC by Microsoft as part of the Visual C++ 2008 Feature Pack and is now distributed with Visual Studio 2010.

Mucking about in CMFCLinkCtrl

When I first saw the item labeled MFC Link Control in the Visual Studio 2010 designer's toolbox, I assumed it corresponded to a class that would incorporate all of the typical properties we've come to associate with hyperlink controls: font typeface and size; hypertext colors (normal, hover, and visited); and of course a way to set the URL and the control's display text. I was surprised to learn that the font and text colors were essentially fixed, which as a minimum were things I wanted to modify so that they would fit in with the rest of my GUI. Usually I want hyperlinks to be underlined, but there are occasionally situations when I want to be able to turn off underlining. Looking over Microsoft's API documentation for the class [^], it appeared to me that all you could do was:

  • Set the URL.
  • Optionally set a URL prefix like mailto: or http: if for some reason you didn't want to include it in the URL itself.
  • Force the hyperlink's focus rectangle to size itself to match the display text for the hyperlink.

Also, of course, since the class is derived from CWnd via CButton and CMFCButton, you can set the display text with CWnd's SetWindowText() member function. This shows up in the Visual Studio designer's property window as the hyperlink's "Caption". And CMFCLinkCtrl inherits from CMFCButton a SetTooltip() member function that lets you easily set the tooltip text for the hyperlink. Like the caption, this can either be set programmatically or it can be set declaratively in the Visual Studio designer's property window for the control.

In addition to these properties, CMFCLinkCtrl also lets you set an "always underlined" property for each instance of the class through a public member variable, m_bAlwaysUnderlineText, although this capability is not documented in Microsoft's API for the class. When m_bAlwaysUnderlineText == FALSE, the hyperlink will be underlined only when the mouse is hovering over it. The default setting is TRUE, giving you an always-underlined hyperlink. I was a bit surprised to see a public member variable used for an important setting with no provision for a propery setter and getter. After several years of programming primarily in C#, using a public member variable directly seems so Twentieth Century. Maybe that's why Microsoft's API documentation for the class doesn't mention it.

For me though, the real deal-breaker with CMFCLinkCtrl is the fact that its font is essentially fixed. To overcome such a limitation in times past I would have implemented OnCtlColor in the code for my GUI window class and changed the font properties associated with each hyperlink object there. With CMFCLinkCtrl this doesn't work because of the fact that the control's font is hard-coded in CMFCLinkCtrl::OnDraw. Had the font assignment been made in the class constructor rather than OnDraw it would have been simple enough to change the font in OnCtlColor.

Even so, there is a way to overcome the font limitation without deriving a new class from CMFCLinkCtrl: in your GUI window class you can place the following code in OnInitDialog or an equivalent function (e.g., OnInitialUpdate or OnShowWindow). For the example shown here, the default hyperlink font would be replaced with 10-point Segoe UI, underlined.

//</span /> ---------------------------------------------------
</span />//</span /> Declare a logfont object and then clear its memory.
</span />//</span /> ---------------------------------------------------
</span />LOGFONT lf;
memset(&lf, 0</span />, sizeof</span />(LOGFONT));
//</span /> ------------------------------------------------------------------------
</span />//</span /> Specify a Segoe UI 10-point font, not italic, normal weight, underlined.
</span />//</span /> ------------------------------------------------------------------------
</span />_tcscpy_s(lf.lfFaceName, LF_FACESIZE, _T("</span />Segoe UI"</span />));
lf.lfHeight = 100</span />;
lf.lfItalic = (BYTE)FALSE;
lf.lfWeight = FW_NORMAL;
lf.lfUnderline = (BYTE)TRUE;
//</span /> -----------------------------------------------------------------------
</span />//</span /> Use the font to replace the afxGlobalData.fontDefaultGUIUnderline font.
</span />//</span /> -----------------------------------------------------------------------
</span />CClientDC dc(this</span />);
afxGlobalData.fontDefaultGUIUnderline.Detach();
VERIFY(afxGlobalData.fontDefaultGUIUnderline.CreatePointFontIndirect(&lf, &dc));

As mentioned in the introduction this works well but has the side effect that all instances of CMFCLinkCtrl will have the new font. If that's exactly what you want, you're done. However, if you want to be able to specify a separate font for each instance of your hyperlink control, read on.

The derived CHyperlink class

Because of the limitations imposed by the CMFCLinkCtrl class, I decided to derive a new class from CMFCLinkCtrl and add what I felt were the missing pieces. To integrate the derived class, CHyperlink, into your code, add the following files to your project. These files are provided in the download for the CHyperlink source files and are also included as part of the download for the full demo project source files.

  • CHyperlink.cpp
  • CHyperlink.h

Then add the following line to any class in which you want to instantiate an object of the CHyperlink class:

#include</span /> "</span />CHyperlink.h"</span />
</span />

In the Visual Studio designer for any window that will include a hyperlink, select the MFC Link Control in the toolbox and drag it to where you want it to appear in the window. In the properties window you can set the caption (display text), tooltip, URL, and URL prefix if you want to handle the prefix separately from the URL. You can alternatively set these properties programmatically. Now, using the Class Wizard create a control member variable associated with the IDC_xxx identifier for the MFC Link Control object. Select CMFCLinkCtrl as the type of the member variable (it will probably be the only choice shown). After you've done that, open the header file for your window class, find the member variable you just created and rename its declaring type from CMFCLinkCtrl to CHyperlink. Et voilà: you now have a CHyperlink object in your window class.

CHyperlink API

The following table summarizes the API for CHyperlink, including some important features inherited from classes further up in the hierarchy. Property setters are listed first, then property getters, then utility functions.

Public Member Function Description
void SetHyperlinkColors(COLORREF colorNormalLink, COLORREF colorHoverLink, COLORREF colorVisitedLink) Sets three hyperlink colors:
  • colorNormalLink – used when the mouse is not hovering over the hyperlink and the link has not previously been visited.
  • colorHoverLink – used when the mouse is hovering over the hyperlink.
  • colorVisitedLink – used when the mouse is not hovering over the hyperlink and the link has previously been visited.
void SetHyperlinkFont(LPCTSTR strFontName, long nFontHeight, BOOL bItalics = FALSE, BOOL bBold = FALSE) Sets the hyperlink font as specified by the typeface name, Windows LOGFONT character height, and whether or not the hyperlink should be shown in italics and/or bold text.
void SetHyperlinkFont(LPCTSTR strFontName, int nPoints, BOOL bItalics = FALSE, BOOL bBold = FALSE) Sets the hyperlink font as specified by the typeface name, character height in points, and whether or not the hyperlink should be shown in italics and/or bold text.
void SetHyperlinkFontToDefault() Sets the hyperlink font to the CMFCLinkCtrl default font. This is the font specified by afxGlobalData.fontDefaultGUIUnderline, which usually points to a logical font, MS Shell Dlg. However, the default font may be changed by programmatically selecting a different font into the framework global variable afxGlobalData.fontDefaultGUIUnderline. See the section above entitled "Mucking about in CMFCLinkCtrl" for information on how to do this.
BOOL SetHyperlinkStyle(HyperlinkStyle enumValue) Sets the hyperlink style to one of four enumeration values describing style options inherited from the ancestor CMFCButton class:
  • TextOnly – the hyperlink is shown as simple text. This is the default used if this function is not called.
  • ThreeDButton – the hyperlink display text is enclosed in an XP-style button.
  • FlatButton – the hyperlink is shown as simple text but appears as a button when the mouse hovers over the hyperlink.
  • SemiFlatButton – the hyperlink display text is enclosed in a flat button.
Returns TRUE if the style was set correctly; FALSE otherwise.
void SetMatchParentFont(BOOL bMatch) If bMatch is TRUE, the hyperlink font will match the font of the parent window when the window first opens. If bMatch is FALSE, the hyperlink will initially be set to the default font specified by the parent CMFCLinkCtrl class. This function is intended to be used before the window first opens, typically in OnInitDialog or an equivalent function. After the window opens, the font may be changed programmatically at any time.

If this function is not called, the default is to assume that the font of the parent window should be matched; i.e., CHyperlink acts as though this function was called with the parameter set to TRUE.
BOOL SetTextAlignment(HyperlinkAlignment enumValue) Sets the text alignment for the hyperlink to one of three enumeration values describing alignment options inherited from the ancestor CMFCButton class: Left, Right, or Center. The default alignment used if this function is not called is Left alignment.

Returns TRUE if the alignment was set correctly; FALSE otherwise.
void SetTooltip(LPCTSTR lpszToolTipText) Inherited from CMFCLinkCtrl. Sets the tooltip text for the hyperlink using the string parameter.
void SetURL(LPCTSTR lpszURL) Inherited from CMFCLinkCtrl. Sets the URL for the hyperlink using the string parameter.
void SetURLPrefix(LPCTSTR lpszPrefix) Inherited from CMFCLinkCtrl. Sets the URL prefix, such as http: or mailto:, using the string parameter. The prefix can alternatively be included within the URL itself (refer to the SetURL function). Obviously you can't include the prefix both places; if you've included it in the URL, don't call this function.
void SetWindowText(LPCTSTR lpszString) Inherited from CWnd. Sets the hyperlink's "caption", or display text, to the value of the string parameter.
void UnderlineHyperlink(BOOL bUnderline = TRUE) Sets a property that determines whether the hyperlink is always underlined (bUnderline is TRUE) or underlined only when the mouse is hovering over the hyperlink (bUnderline is FALSE). The default is to always underline the hyperlink so if that is the desired behavior it is not necessary to call this function.
CString GetHyperlinkFontName() Returns a CString with the name of the font currently being used for the hyperlink. If the name is that of one of the logical fonts MS Shell Dlg or MS Shell Dlg 2, the function attempts to obtain the name of the corresponding actual font installed on the user's computer by querying the Windows registry. If it succeeds, that is the font name returned; otherwise the name of the logical font is returned.
int GetHyperlinkFontPoints() Returns an integer representing the height, in points, of the font currently being used for the hyperlink.
BOOL GetMatchParentFont() Returns TRUE if the initial font has been set to match the font of the parent window, either by explicitly calling SetMatchParentFont(TRUE) before the window first opens, or by not calling SetMatchParentFont() at all, since by default it is assumed that the hyperlink font should match the font of the parent window.

Returns FALSE if SetMatchParentFont(FALSE) has been called, which will result in the CMFCLinkCtrl default font being used when the window first opens.
BOOL IsHyperlinkFontItalic() Returns TRUE if the font currently being used for the hyperlink has an italic typeface; FALSE otherwise.
BOOL IsHyperlinkFontBold() Returns TRUE if the font currently being used for the hyperlink has a LOGFONT lfWeight value of FW_BOLD or greater; FALSE otherwise.
CSize SizeToContent(BOOL bVCenter = FALSE, BOOL bHCenter = False) Adjust the hyperlink's focus rectangle to the size of the hyperlink's display text. Six pixels of padding are used between the text and the focus rectangle. If bVCenter is TRUE the display text will be centered vertically between the top and bottom of the hyperlink control; the default is FALSE. If bHCenter is TRUE the display text will be centered horizontally between the left and right sides of the hyperlink control; the default is FALSE. Usually the bHCenter parameter can be left at its default value because the SetTextAlignment() function is available to align the text within the left and right margins of the control if that is desired.

The return value is a CSize object containing the width and height of the hyperlink's focus rectangle in pixels.

CHyperlink has two public enumerations. Although the parent class, CMFCLinkCtrl, and its parent, CMFCButton, include a fairly large number of public member variables between them, CHyperlink provides property setters and getters for most of those variables as outlined in the table above so the variables themselves are not documented here. If you have reason to use the public member variables directly you'll have to look them up in the documentation for those classes. Some of the public member variables aren't documented in MSDN so you'll have to look in the source code, which you'll find in the source-code folder for the Visual Studio 2010 MFC classes. The folder should be located at %ProgramFiles%\Microsoft Visual Studio 10.0\VC\atlmfc\src\mfc. The files for the two parent classes are named afxlinkctrl.cpp and afxbutton.cpp. The corresponding header files are at %ProgramFiles%\Microsoft Visual Studio 10.0\VC\atlmfc\include.

Public Enumeration Description
enum HyperlinkStyle { TextOnly, ThreeDButton, FlatButton, SemiFlatButton }; This enumeration has four values as shown at left and described in the table above under SetHyperlinkStyle. To use the enumeration in your program you don't refer to the enumeration variable itself but rather to one of the four values; as an example, if you have instantiated a CHyperlink object and associated it with a control member variable named m_ctlLINK you could use an enumeration value to set the hyperlink style as follows:

m_ctlLINK.SetHyperlinkStyle(m_ctlLINK.ThreeDButton);
enum HyperlinkAlignment { Left, Right, Center }; This enumeration has three values as shown at left and described in the table above under SetTextAlignment. To use the enumeration in your program you don't refer to the enumeration variable itself but rather to one of the three values; as an example, if you have instantiated a CHyperlink object and associated it with a control member variable named m_ctlLINK you could use an enumeration value to set the hyperlink alignment as follows:

m_ctlLINK.SetTextAlignment(m_ctlLINK.Center);

Coding CHyperlink

This section examines some of the programming aspects for the CHyperlink class, so it's time to look at some code. First, the class constructor sets several member variables to default values and also sets the initial hyperlink font to the same default font used by CMFCLinkCtrl. The difference here is that we do it in the constructor rather than in OnDraw so it can easily be changed. The class destructor simply deletes the two font objects that are used by CHyperlink.

//</span /> Constructor
</span />CHyperlink::CHyperlink()
{
m_bHLinkShown = FALSE;    //</span /> Becomes TRUE after the control has been shown the first time.
</span />m_pParent = NULL;        //</span /> Becomes non-NULL after the parent window exists.
</span />m_bMatchParentFont = TRUE;    //</span /> Assume the user wants to initially match the parent font.
</span />m_nAlignStyle = ALIGN_LEFT;    //</span /> Assume the user wants to align the text on the left.
</span />
//</span /> -------------------------------------
</span />//</span /> Set default colors for the hyperlink.
</span />//</span /> -------------------------------------
</span />m_colorNormalLink  = afxGlobalData.clrHotLinkNormalText;
m_colorHoverLink   = afxGlobalData.clrHotLinkHoveredText;
m_colorVisitedLink = afxGlobalData.clrHotLinkVisitedText;

//</span /> ----------------------------------------------------------------------------------------
</span />//</span /> Initially set the default underlined GUI font as the hyperlink font. This normally uses
</span />//</span /> a Microsoft "logical" font, MS Shell Dlg. The first time OnDraw() is called we'll change
</span />//</span /> this to the parent window's font but we don't know it yet so we can't do that now.
</span />//</span /> ----------------------------------------------------------------------------------------
</span />LOGFONT lf;
afxGlobalData.fontDefaultGUIUnderline.GetLogFont(&lf);
BOOL bBold = lf.lfWeight ></span /> FW_NORMAL ? TRUE : FALSE;
SetHyperlinkFont(lf.lfFaceName, lf.lfHeight, lf.lfItalic, bBold);
}

//</span /> Destructor
</span />CHyperlink::~CHyperlink()
{
m_Font.DeleteObject();    //</span /> Delete the normal font.
</span />m_ULFont.DeleteObject();    //</span /> Delete the underlined font.
</span />}

Note that the constructor calls one of the two SetHyperlinkFont overrides described in the API table above to create the default font. Notice also that the constructor sets m_bMatchParentFont = TRUE. This means that the default action for the hyperlink is to use the same font as the parent window. However, when the constructor is invoked there is no guarantee that we can determine the font of the parent window, since the window might not yet exist. Therefore we temporarily set the font to whatever is specified by afxGlobalData.fontDefaultGUIUnderline. The first time OnDraw is called, we'll get the parent window's font and set the hyperlink font based on that. That is, unless the parent program has previously called CHyperlink::SetMatchParentFont(FALSE); in that case we leave the font set to whatever the calling program may have specified. If the parent program called CHyperlink::SetMatchParentFont(FALSE) without specifying a font, we leave the font at the default value set in the constructor. This is the same effect you would get if you used CMFCLinkCtrl rather than CHyperlink, except that with the latter you have the option of changing the font or its color and other properties later if you wish.

Implementing OnDraw

Below is the code for OnDraw. Notice that in addition to setting the font, we also set the hyperlink's text color before drawing the text. The color value used depends on whether the mouse is currently hoving over the hyperlink and whether or not the link has previously been visited. These values can be set by the calling program through the SetHyperlinkColors API function. Similarly, the calling program may have changed the text alignment by calling the SetTextAlignment API function. The switch statement immediately before the call to DrawText is used to set the alignment according to whatever may have been specified by the parent program.

void</span /> CHyperlink::OnDraw(CDC* pDC, const</span /> CRect& rect, UINT uiState)
{
ASSERT_VALID(pDC);
m_pParent = GetParent();

//</span /> ---------------------------------------------------------------------------
</span />//</span /> Font initialization--do this exactly once, the first time OnDraw is called.
</span />//</span /> ---------------------------------------------------------------------------
</span />if</span /> (!m_bHLinkShown)
    {
    //</span /> ----------------------------------------------------------------------------
</span />    //</span /> If m_bMatchParentFont has been set, inherit the font from the parent window.
</span />    //</span /> ----------------------------------------------------------------------------
</span />    if</span /> (m_bMatchParentFont)
        {
        if</span /> (m_pParent != NULL)
            {
            LOGFONT lf;
            m_pParent-></span />GetFont()-></span />GetLogFont(&lf);    //</span /> Get the dialog font.
</span />
            BOOL bBold = lf.lfWeight ></span /> FW_NORMAL ? TRUE : FALSE;

            SetHyperlinkFont(       //</span /> Set the font for the hyperlink:
</span />                lf.lfFaceName,      //</span />   ... Name of the dialog font.
</span />                lf.lfHeight,        //</span />   ... Font height from the parent.
</span />                (BOOL)lf.lfItalic,  //</span />   ... Italics if used in parent.
</span />                bBold);             //</span />   ... Bold if parent has bold.
</span />
            }
        }
    m_bHLinkShown = TRUE;    //</span /> Don't do this again.
</span />    }

//</span /> ------------------------
</span />//</span /> Set the font to be used.
</span />//</span /> ------------------------
</span />CFont* pOldFont = NULL;
if</span /> (m_bAlwaysUnderlineText || m_bHover)
    {
    if</span /> (&m_Font != NULL)
        {
        //</span /> ----------------------------------------------------------
</span />        //</span /> Use the font set by the user with underlining added to it.
</span />        //</span /> ----------------------------------------------------------
</span />        if</span /> (!m_bULCreated)
            {
            CreateUnderlinedFont(&m_Font);
            }
        pOldFont = pDC-></span />SelectObject(&m_ULFont);
        }
    else</span />
        {
        //</span /> ------------------------------------------------------------
</span />        //</span /> No font set by the user, so use the default underlined font.
</span />        //</span /> ------------------------------------------------------------
</span />        pOldFont = pDC-></span />SelectObject(&afxGlobalData.fontDefaultGUIUnderline);
        }
    }
else</span />
    {
    //</span /> --------------------
</span />    //</span /> Not using underline.
</span />    //</span /> --------------------
</span />    if</span /> (&m_Font != NULL)
        {
        //</span /> -----------------------------
</span />        //</span /> Use the font set by the user.
</span />        //</span /> -----------------------------
</span />        pOldFont = pDC-></span />SelectObject(&m_Font);
        }
    else</span />
        {
        //</span /> --------------------------------------------------------
</span />        //</span /> No font set by the user, so use the default button font.
</span />        //</span /> --------------------------------------------------------
</span />        pOldFont = CMFCButton::SelectFont(pDC);
        }
    }

//</span /> -------------------------------
</span />//</span /> Set the hyperlink's text color.
</span />//</span /> -------------------------------
</span />pDC-></span />SetTextColor(m_bHover ? m_colorHoverLink : (m_bVisited ? m_colorVisitedLink : m_colorNormalLink));

//</span /> -----------------------------------------------------------------------------------
</span />//</span /> Set the background mode to TRANSPARENT so the background will show behind the text.
</span />//</span /> -----------------------------------------------------------------------------------
</span />pDC-></span />SetBkMode(TRANSPARENT);

//</span /> -----------------------------
</span />//</span /> Get the text for the control.
</span />//</span /> -----------------------------
</span />CString strLabel;
GetWindowText(strLabel);

//</span /> --------------
</span />//</span /> Draw the text.
</span />//</span /> --------------
</span />CRect rectText = rect;
if</span /> (m_bMultilineText)
    {
    pDC-></span />DrawText(strLabel, rectText, DT_WORDBREAK | DT_TOP);
    }
else</span />
    {
    UINT nFormat = DT_SINGLELINE | DT_WORD_ELLIPSIS | DT_VCENTER;
    switch</span />(m_nAlignStyle)
        {
        case</span /> ALIGN_CENTER: nFormat |= DT_CENTER; break</span />;
        case</span /> ALIGN_RIGHT:  nFormat |= DT_RIGHT;  break</span />;
        case</span /> ALIGN_LEFT:
        default</span />:
            nFormat |= DT_LEFT;
            break</span />;
        }
    pDC-></span />DrawText(strLabel, rectText, nFormat);
    }

pDC-></span />SelectObject(pOldFont);
}

One thing I want to point out in the above code is the fact that I didn't spend much time fretting over multi-line hyperlinks, since I don't think I've ever used one. In the code above I've just done essentially what CMFCLinkCtrl does with multi-line hyperlinks, which isn't much. If multi-line hyperlinks are important to you, it might be necessary for you to spend some time revising that section of OnDraw to support them more fully.

Dealing with Logical Fonts

Most of the code in CHyperlink.cpp is pretty straightforward and well-commented, and since I've classified this as an intermediate article I'm not going to spend much time discussing code that most MFC programmers will already be comfortable with. There shouldn't be much in the property setters and getters that anyone would find unusual. However, I'll look briefly at the GetHyperlinkFontName function because of the special handling required for the MS Shell Dlg and MS Shell Dlg 2 logical fonts. These logical fonts were new to me when I started looking at the CMFCLinkCtrl code, so they may be new to others as well. Here is the code for GetHyperlinkFontName:

CString CHyperlink::GetHyperlinkFontName()
{
CString strLogName;
LOGFONT lf;
memset(&lf, 0</span />, sizeof</span />(LOGFONT));
if</span /> (&m_Font != NULL)
    {
    m_Font.GetLogFont(&lf);
    }
else</span />
    {
    afxGlobalData.fontDefaultGUIUnderline.GetLogFont(&lf);
    }
strLogName = lf.lfFaceName;
if</span /> ((strLogName == _T("</span />MS Shell Dlg"</span />)) || (strLogName == _T("</span />MS Shell Dlg 2"</span />)))
    {
    //</span /> ------------------------------------------------------------------------------------------------
</span />    //</span /> The font is one of Microsoft's two logical fonts, so get the actual font name from the registry.
</span />    //</span /> ------------------------------------------------------------------------------------------------
</span />    CRegKey regKey;
    CString strKey = _T("</span />SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes\\"</span />);
    if</span /> (regKey.Open(HKEY_LOCAL_MACHINE, strKey, KEY_QUERY_VALUE) == ERROR_SUCCESS)
        {
        TCHAR szName[256</span />];
        ULONG nChars = 256</span />;
        if</span /> (regKey.QueryStringValue((LPCTSTR)strLogName, szName, &nChars) == ERROR_SUCCESS)
            {
            int</span /> nLen = _tcslen(szName);
            //</span /> -----------------------------------------------------------------
</span />            //</span /> Make sure the number of chars returned matches the string length.
</span />            //</span /> -----------------------------------------------------------------
</span />            if</span /> (nChars == (ULONG)(nLen + 1</span />))
                {
                regKey.Close();
                return</span /> szName;
                }
            //</span /> ---------------------------------------------------------------------
</span />            //</span /> Wrong count of chars returned; drop through to avoid security issues.
</span />            //</span /> ---------------------------------------------------------------------
</span />            }
        //</span /> --------------------------------
</span />        //</span /> Couldn't query the string value.
</span />        //</span /> --------------------------------
</span />        }
    //</span /> -------------------------------
</span />    //</span /> Couldn't open the registry key.
</span />    //</span /> -------------------------------
</span />    regKey.Close();
    }
//</span /> ------------------------------------------------------------------------------------
</span />//</span /> Not a logical font, or we couldn't get the substitute, so just return the font name.
</span />//</span /> ------------------------------------------------------------------------------------
</span />return</span /> strLogName;
}

According to the MSDN documentation on them [^], MS Shell Dlg and MS Shell Dlg 2 are not true fonts but rather are logical typeface names that the operating system associates with specific fonts depending on the user's locale and the version of Windows being used. For most purposes the logical names can be used as though they were font names and Windows will function quite happily with them. However, those two names will never appear in a list of fonts installed on a user's system, because they aren't real fonts and therefore they can't be installed. Consequently, in the GetHyperlinkFontName function above we don't want to return either of those names. Instead, if GetLogFont returns a LOGFONT structure with lfFaceName set to either of the logical typeface names, we go into the registry to find the actual font name that Windows substitutes for the logical font. That's the name we return.

The demo program provided with this article as a download demonstrates how this capability can be used. If you click the "Default Font" button near the bottom of the dialog window (see the illustration at the beginning of the article), the name in the font combo box will change to whatever is equivalent on your computer to MS Shell Dlg. In many cases this will be Microsoft Sans Serif, but in some locales and older versions of Windows it will differ from that. See the documentation mentioned in the previous paragraph for details.

The SizeToContent Function

This function resizes the hyperlink's focus rectangle to fit around the actual text of the hyperlink. It had to be overridden because the original in CMFCLinkCtrl again uses a hard-coded reference to the logical font regardless of what font is actually being used for the hyperlink. The code is pretty straightforward so I'm not including it here; you can check the CHyperlink.cpp file in the download if you want the details. The main thing I want to mention is that in the case of image and multi-line hyperlinks all I've done is pass the job on to either CMFCButton, for image hyperlinks, or to CMFCLinkCtrl, for multi-line hyperlinks. I haven't spent much time testing either of these capabilities because I assume they work as advertised in the MSDN documentation. But if you need to use either of those types of hyperlink you might need to make some additional modifications to the code in this function.

Supporting Accessibility

The final thing to mention about the CHyperlink class is that I've implemented PreTranslateMessage in order to provide some additional accessibility functionality. The place where I work has a policy of making as much code as possible accessible through keyboard shortcuts. CMFCLinkCtrl already does this because if you press the return key when the hyperlink has the focus, it will execute the jump. However, it's more common to provide that functionality with the space bar rather than the return key. Therefore, PreTranslateMessage converts VK_SPACE keystrokes into VK_RETURN values and passes them through to CMFCLinkCtrl::PreTranslateMessage. This might not work the way you expect if your dialog window has a default button. In that case, instead of passing a VK_RETURN value to the parent class, you could replace it with the following code:

SendMessage(WM_COMMAND, MAKEWPARAM(this</span />-></span />GetDlgCtrlID(), BN_CLICKED), (LPARAM)this</span />-></span />m_hWnd);
return</span /> TRUE;

Using CHyperlink in a Program

The downloads for this article include a demo project, which I've provided both as an executable file and as a Visual Studio 2010 solution. The demo is a dialog-based MFC application designed to demonstrate the capabilities of the CHyperlink class. Nearly everything of importance happens in the dialog class. The image at the beginning of this article shows the demo window in operation. Because the demo is not the main focus of this article I'll just provide a brief overview of it here along with a few comments on points of interest that surfaced while I was working on the demo.

Immutable and Mutable Hyperlinks

There are two hyperlinks in the dialog window of the demo program, both going to the same web page and both having the same display text. The first one, which I've referred to as "Immutable", is a CMFCLinkCtrl object. The second, called "Mutable", is a CHyperlink object that by default uses the dialog's default font, 9-point Verdana. Because the immutable hyperlink uses the afxGlobalData.fontDefaultGUIUnderline font you can compare the two objects as you change fonts and other settings for the mutable hyperlink. If you click the "Default Font" button near the bottom of the window the two hyperlinks should be identical.

Dialog Background

In case you're wondering why I recolored the dialog window's background instead of using the default background color, it's because with the default background the border of the group box was virtually invisible except on Windows XP. I'm not sure why the design gurus at Microsoft would create a theme that causes the group box to lose the purpose for which it was invented, but that's what they seem to have done. Since you can't recolor the border of the group box without going to quite a lot of trouble, the easiest thing to do is recolor the window's background so that the border shows up clearly.

Options for the Mutable Hyperlink

Now that you can see the border of the "options" group box, here is an illustration showing the dialog window with the hyperlink as a 3D button and several font settings changed from their default values.

Demo dialog showing the mutable hyperlink as a 3D button

The changes you can make to any of the options that will affect the mutable hyperlink are listed below:

  • Select a new font name.
  • Change the point size of the font.
  • Select italics or bold text.
  • Set underlining to appear only when the mouse is hovering over the hyperlink.
  • Change the hyperlink from the default text-only display to one of the three button styles supported by the CMFCButton class. Of the button styles, only the XP-style 3D button is likely to get very much use; the other two, in my view, are rather unattractive.
  • Change the alignment of the font within its focus rectangle from the default left alignment to either center or right alignment. If you select one of the button styles for the hyperlink you'll probably want to change this to center alignment.
  • Change any or all of the three hyperlink colors: Normal, Hover, and Visited. I'll discuss the controls used for these color pickers below.
  • Click buttons to revert to either the dialog font or to the default font used by the immutable hyperlink. This not only updates the hyperlink but also the selections in the font combo box and in the point-size combo box.

Expanding Window

If you select one of the larger font sizes you'll notice that the dialog window expands its width as needed to display the entire hyperlink. Reset the font to a small point size and the window will contract again. You can also enlarge or reduce the window by dragging any of the borders. However, it will only contract to the original display size and no smaller. This is controlled by OnGetMinMaxInfo, a handler for the WM_GETMINMAXINFO message. If you don't know how such a handler works, take a look at the code for that handler in the Demo_HyperlinkDlg.cpp file.

Additional VC++ 2008 Feature Pack Controls

In addition to CMFCLinkCtrl and its derivative CHyperlink, the demo program uses two additional controls that were added to the MFC stable as part of the Visual C++ 2008 Feature Pack: CMFCFontComboBox and CMFCColorButton. Both of these are quite useful and warrant at least a passing look.

The CMFCFontComboBox Control

The font combo box automatically loads a combo box with a list of all fonts installed on the user's system. The combo box displays each typeface name along with an icon showing whether it is a TrueType, Raster, or Device font (you can set the combo box to display any or all of these types). This makes it very easy for the user to change fonts in your application. Here I've bound the combo box to the mutable hyperlink so that selecting a different font name immediately changes the font used by the hyperlink. I've also bound it to the "Dialog Font" and "Default Font" buttons so that when either button is clicked, the relevant font name is selected in the combo box.

The CMFCColorButton Control

This is an interesting color-picker control. The demo program uses three of these controls to let users change the Normal, Hover, and Visited color properties of the mutable hyperlink. When clicked, the color button opens a CMFCColorBar window displaying a selection of 20 colors as shown in the illustration here. The user can click one of these pre-selected colors, or can click the "other" button (shown with the label "More colors...") at the bottom of the window to open a more comprehensive, two-tab color picker. The "standard" tab of the color picker provides a fairly wide selection of colors, and the "custom" tab lets you select or specify numerically any color from the full gamut of colors.

CMFCColorBar window

One oddity I encountered with this control is the fact that it isn't fully supported by the Visual Studio 2010 application wizard if you are linking with MFC as a static library. The wizard neglects to add the following required code to the project's .rc file:

#ifndef _AFXDLL
#include</span /> "</span />afxribbon.rc"</span /> // Ribbon and control bars.
</span />
#endif</span />

As a result, clicking the "More colors..." button at the bottom of the CMFCColorBar window has no effect. I was able to solve the issue by adding the above code to my .rc2 file. I reported this issue on Microsoft® Connect and received a reply from Pat Brenner that the problem had already been identified and fixed for the next major release of Visual Studio. In the meantime, if you link with the MFC static library and want to use this control, you'll need to add the code above to your .rc2 file. For more information you can read my report and Brenner's reply [^].

Accessibility Support

When I last used MFC on a full-time basis before switching primarily to C#, Visual Studio 6.0 was still widely used and supporting keyboard shortcuts in dialog windows required a significant amount of effort. While working on this project with Visual Studio 2010 I was pleased to discover that this has changed; you can now add keyboard shortcuts with very little effort to almost any control in a dialog window. In the demo application my strategy for doing this was to include an ampersand character (&) in a static label associated with each control except buttons. The static label has to immediately precede the control of interest in the tab order but the static label's Tabstop property must be set to FALSE. As usual the ampersand immediately precedes the character to be used with the Alt key as the keyboard shortcut. For buttons the ampersand character can be included directly in the button caption. "Buttons" of course include check boxes.

In this way I was able to add keyboard shortcuts for almost every control in the dialog window without writing any actual code. In the demo project you can see the shortcut characters by pressing the Alt key; this causes the shortcut characters to be underlined. Then execute a particular shortcut by pressing Alt together with the shortcut key. In the case of the hyperlink controls, this selects the hyperlink but doesn't execute it. To do that you have to press either the return key or the space bar (but the latter only works with the CHyperlink object; that is, the mutable hyperlink).

As an aside, since the two hyperlink controls in the dialog both inherit from CMFCButton, technically you could add an ampersand character within the display text to define keyboard shortcuts for those controls. The underlined character would only be visible if you turned off underlining for the hyperlink, however, so instead I defined the shortcuts for the hyperlinks in their associated static labels.

No Keyboard Support for CMFCColorBar

The one control I couldn't fully support with keyboard shortcuts is the CMFCColorBar window that opens when you click the CMFCColorButton control. The color button itself works fine; to see this, press Alt+N to select the Normal color button. Then press the space bar to open the associated color bar window. But if you want to go any farther than that with the keyboard, you're out of luck; there is no support for using keyboard characters to select a color from that window or to click the "More colors..." button using a shortcut key. (No, the ampersand doesn't work if you add it to the caption of the "More colors..." button).

There's undoubtedly a solution for this that could be implemented by deriving a new class from CMFCColorBar, but that isn't the primary focus of this article and I didn't want to make a project out of it. Instead I'll just issue an open invitation to anyone who might be interested in adding keyboard shortcuts to that control, which otherwise I find very useful.

History

Version 1.0 – 2010 October 10

  • Initial public release

Version 1.1 – 2011 January 11

  • Derived the CMFCHyperlink class from CWinAppEx rather than from CWinApp in order to fix a memory leak with CMFCVisualManager. See the comment below from buingocdinh and my response to it.

License

This article, along with the associated source code and files, is licensed under the Code Project Open License (CPOL). You are free to use this software in any way you like, except that you may not sell the source code. The software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software might cause.

Please note that the code provided with this article is derived from software owned and copyrighted by Microsoft® as part of the Microsoft Foundation Classes for Visual C++®. As such it is a derivative work that could be subject to more restrictive licensing policies from Microsoft®.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Dennis Dykstra
Software Developer (Senior)
United States United States
No, I don't often wear a tie.

Comments and Discussions

 
BugCannot call SizeToContent from within OnInitDialog PinmemberCharles Oppermann1-Aug-11 12:09 
GeneralRe: Cannot call SizeToContent from within OnInitDialog PinmemberDennis Dykstra1-Aug-11 13:47 
GeneralRe: Cannot call SizeToContent from within OnInitDialog PinmemberCharles Oppermann2-Aug-11 19:23 
GeneralRe: Cannot call SizeToContent from within OnInitDialog PinmemberDennis Dykstra4-Aug-11 17:55 
GeneralParent Font Charset is cleared PinmemberGranant5-Apr-11 3:18 
GeneralRe: Parent Font Charset is cleared PinmemberDennis Dykstra5-Apr-11 4:25 
GeneralMy vote of 1 Pinmembersheds11-Jan-11 22:20 
QuestionRe: Your vote of 1 PinmemberDennis Dykstra12-Jan-11 4:33 
GeneralRe: My vote of 1 PinmemberRalf Wirtz12-Jan-11 9:33 
QuestionRe: My vote of 1 PinmemberAslam_Iqbal13-Jan-11 3:03 
AnswerRe: My vote of 1 PinmemberDennis Dykstra13-Jan-11 4:26 
GeneralRe: My vote of 1 PinmemberRalf Wirtz13-Jan-11 7:57 
GeneralLeak memory Pinmemberbuingocdinh10-Jan-11 23:23 
AnswerRe: Leak memory PinmemberDennis Dykstra11-Jan-11 11:46 
GeneralRe: Leak memory Pinmemberbuingocdinh11-Jan-11 15:21 
GeneralMy vote of 5 PinmemberMember 143041119-Oct-10 23:09 
GeneralRe: My vote of 5 PinmemberDennis Dykstra21-Oct-10 3:41 
GeneralMy vote of 5 PinmemberMember 112587818-Oct-10 21:10 
GeneralRe: My vote of 5 PinmemberDennis Dykstra19-Oct-10 9:28 
GeneralMy vote of 5 PinadminChris Maunder13-Oct-10 2:56 
GeneralRe: My vote of 5 PinmemberDennis Dykstra14-Oct-10 17:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 11 Jan 2011
Article Copyright 2010 by Dennis Dykstra
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid