A Better(?) (Hyper)Link Control






4.88/5 (24 votes)
An article that describes yet another hyperlink control
Introduction
This is my implementation of a (hyper)link control. I have been sitting
on it for a while, and finally decided to release it. While there are may
other implementations of link controls out there, none of the ones that I have
found really act like the ones used in Internet Explorer®. For
example, many of them are based on the Static
control, and as such are unable to
be focused using the keyboard, which means that they are useless without a mouse
(accessibility issue), and have incorrect click behavior.
My implementation of the link control offers some features not commonly found in other implementations:
- It is based on (subclasses) the Button control, so it can be focused and navigated just like any other control
- It can be activated by the keyboard (using the
<ENTER>
key) - It correctly activates on the Button-Up, not the Button-Down
- It contains a built-in ToolTip
- It draws and behaves correctly when disabled
- Its colors and fonts are completely customizable
- It supports the ability to launch both URLs and the contents of a
SHELLEXECUTEINFO
structure (see below) - It can be dragged and dropped just like a link in Internet Explorer (try dragging it from the demo application to an instance of IE or the Desktop)
- It uses
WM_NOTIFY
-based notification messages (the Win32 way) to notify the parent of mouse activity, and gives the parent the ability to deny an activation request, or cancel a Drag-n-Drop operation, etc.
Background
To fully understand the benefits this implementation of the hyperlink control has to offer, experiment with the links on this page using your browser. Can you navigate them using the mouse and keyboard? How do you activate the control with the mouse; on the Button-Down or the Button-Up? What happens when you Button-Down over the control, but move the mouse off of it and then Button-Up? What happens when you Button-Down somewhere else, but then Button-Up over the control? Etc.
Using The Control
The code for the CJRTSLinkCtrl
class is wrapped up in the
JRTSLinkCtrl.cpp
and JRTSLinkCtrl.h
files. The
CJRTSLinkCtrl
class is used to subclass an existing Button control, just
as you would normally use the CButton
class. For best ease of
use, rebuild your ClassWizard database and include the code for this
class when you do. That way, you will be able to bind a
CJRTSLinkCtrl
class from within ClassWizard.
Now, let us explore
the class by going through its header file from top to bottom. We will
start with the WM_NOTIFY
-based Notification Messages and
the
NMHDR
-Based Notification Structure.
const UINT NMLC_DBLCLICK = NM_DBLCLK; // Double-Click Notification Message const UINT NMLC_SETFOCUS = NM_SETFOCUS; // Control Has Gained The Input Focus const UINT NMLC_KILLFOCUS = NM_KILLFOCUS; // Control Has Lost The Input Focus const UINT NMLC_RCLICK = NM_RCLICK; // Right-Click In Control const UINT NMLC_RDBLCLICK = NM_RDBLCLICK; // Double-Right-Click In Control const UINT NMLC_ACTIVATE = ( NM_FIRST - 90 ); // Link Activate Notification Message const UINT NMLC_MOUSEENTER = ( NM_FIRST - 89 ); // Mouse Enter Link Notification const UINT NMLC_MOUSEHOVER = ( NM_FIRST - 88 ); // Mouse Hover Notification const UINT NMLC_MOUSELEAVE = ( NM_FIRST - 87 ); // Mouse Leave Notification const UINT NMLC_BEGINDRAG = ( NM_FIRST - 86 ); // Begin Drag Operation Notification const UINT NMLC_ENDDRAG = ( NM_FIRST - 85 ); // End Drag Operation Notification const UINT NMLC_BEGINRDRAG = ( NM_FIRST - 84 ); // Begin RDrag Operation Notification const UINT NMLC_ENDRDRAG = ( NM_FIRST - 83 ); // End RDrag Operation Notification const UINT NMLC_MCLICK = ( NM_FIRST - 82 ); // MButton Click const UINT NMLC_MDBLCLICK = ( NM_FIRST - 81 ); // MButton Double-Click struct NMLINKCTRL // Link Control's Notification Object { NMHDR m_hdr; // Base NMHDR Object DWORD m_dwRetVal; // Return Value (Used by NMLC_ACTIVATE) DWORD m_dwFlags; // Message Flags POINT m_ptWhere; // Cursor Position (Screen) };
All of these notification messages are handled using CWnd::OnNotify(...)
or by using the ON_NOTIFY(...)
message map macros. The
NMLINKCTRL
structure is the NMHDR
-based structure that is
passed in the NMHDR
pointer argument to the handler
functions.
Notification Message | Description |
NMLC_DBLCLICK
|
This notification message is used to notify the parent that a
Double-Click has occurred in the Link Control. |
NMLC_SETFOCUS
|
This notification message
is used to notify the parent that the Link Control has gained the input focus.
|
NMLC_KILLFOCUS
|
This notification message is used to notify the
parent that the Link Control has lost the input focus. |
NMLC_RCLICK
|
This notification message
is used to notify the parent that a Right-Click has occurred in the Link
Control. |
NMLC_RDBLCLICK
|
This notification message is used to notify the
parent that a Double-Right-Click has occurred in the Link Control |
NMLC_ACTIVATE
|
This notification message is used to
notify the parent that the Link Control is about to be activated. This
also gives the Parent control a last chance to set/change the Target of the
link, or to deny activation: upon return of the notification call, if
m_dwRetVal is set to FALSE , the link will not be
activated. |
NMLC_MOUSEENTER
|
This notification message is used to
notify the parent that the mouse has entered the Link Control's client area. |
NMLC_MOUSEHOVER
|
This notification message is used to notify the
parent that the mouse has hovered over the Link Control's client area.
(Note that this does not use the standard NM_HOVER message.
This is because the standard NM_HOVER notification supports
preventing a "hover-action" from taking place, and the Link Control does not
support that functionality.) |
NMLC_MOUSELEAVE
|
This notification message is used to notify the parent that the mouse has
left the Link Control's client area |
NMLC_SETFOCUS
|
This notification message
is used to notify the parent that the Link Control has gained the input focus.
|
NMLC_KILLFOCUS
|
This notification message is used to notify the
parent that the Link Control has lost the input focus. |
NMLC_BEGINDRAG
|
This notification message is used to notify the
parent that a Drag-n-Drop operation is about to begin. This also gives
the Parent control a last chance to prevent the initiation of the Drag-n-Drop
operation: upon return of the notification call,
if m_dwRetVal is set to FALSE , the Drag-n-Drop
operation will not be initiated. |
NMLC_ENDDRAG
|
This notification
message is used to notify the parent that a Drag-n-Drop Operation has
completed. The m_dwRetVal member of the
NMLINKCTRL structure contains the result of the Drag-n-Drop operation,
which will be one of the DROPEFFECT_* values. |
NMLC_BEGINRDRAG
|
This notification message is used to notify the
parent that a Right-Button Drag-n-Drop operation is about to begin. This also gives
the Parent control a last chance to prevent the initiation of the Drag-n-Drop
operation: upon return of the notification call,
if m_dwRetVal is set to FALSE , the Drag-n-Drop
operation will not be initiated. |
NMLC_ENDRDRAG
|
This notification
message is used to notify the parent that a Right-Button Drag-n-Drop Operation has
completed. The m_dwRetVal member of the
NMLINKCTRL structure contains the result of the Drag-n-Drop operation,
which will be one of the DROPEFFECT_* values. |
NMLC_MCLICK
|
This notification message
is used to notify the parent that a Middle-Click has occurred in the Link
Control. |
NMLC_MDBLCLICK
|
This notification message is used to notify the
parent that a Double-Middle-Click has occurred in the Link Control |
All of the Notification Messages support the following Flags.
The Flags are used to indicate which usual keyboard modifiers are down at
the time that the message was sent. The Right and Left-specific
flags are only supported on platforms that can distinguish between them.
The last three Flags are Left/Right neutral, so when it doubt, use them:
const DWORD LCF_RCTRL = 0x00000001; // Right-CTRL Key Flag const DWORD LCF_LCTRL = 0x00000002; // Left-CTRL Key Flag const DWORD LCF_RSHIFT = 0x00000004; // Right-SHIFT Key Flag const DWORD LCF_LSHIFT = 0x00000008; // Left-SHIFT Key Flag const DWORD LCF_RALT = 0x00000010; // Right-ALT Key Flag const DWORD LCF_LALT = 0x00000020; // Left-ALT Key Flag const DWORD LCF_CTRL = 0x00000040; // The/A CTRL Key Flag const DWORD LCF_SHIFT = 0x00000080; // The/A SHIFT Key Flag const DWORD LCF_ALT = 0x00000100; // The/A ALT Key Flag
Next, let us look
at the styles supported by the control:
const DWORD LCS_HOTTRACK = 0x00000001; // HotTracking Style const DWORD LCS_DRAGDROP = 0x00000002; // Drag-N-Drop Style const DWORD LCS_VISITED = 0x00000004; // Visited Style const DWORD LCS_TOOLTIPS = 0x00000010; // ToolTips Style
Style | Description |
LCS_HOTTRACK
|
This style enables Hot-Tracking for the control.
|
LCS_DRAGDROP
|
This style enables the Drag-n-Drop feature of the
control. Drag-n-Drop operations can only be initiated from the Link
Area of the control, not just the Client Area. That is the tradeoff
that I decided on between the behavior of a (checkbox-style) button
and an actual Link control. |
LCS_VISITED
|
This
style sets the "Visited" flag for the Link. "Visited" links are shown in
a different color. Normally, links become "Visited" the first time they
are Activated. |
LCS_TOOLTIPS
|
This style enables the control's
built-in ToolTip. |
All of the style bits can be turned on or off at any time and will have a more-or-less immediate effect.
There are also other style bits in the header
file that are prefixed with LCS_I_
. These are internally
used style bits, and cannot be set/reset externally.
Now we come to the fun stuff... The public class members:
// Attributes public: void SetTarget( LPCTSTR cpTarget ); // Set Target As A URL void SetTarget( const SHELLEXECUTEINFO &seiSEI ); // Set Target As SEI Information void SetDisplay( LPCTSTR cpDisplay ); // Set Display Text void SetToolTip( LPCTSTR cpToolTip ); // Set ToolTip Text, If Any void SetNormalFont( const LOGFONT &lfNormalFont ); // Set "Normal" Font void SetULFont( const LOGFONT &lfULFont ); // Set "Underline"/"HotTracked" Font DWORD GetLastActivateError( void ); // Return/Error Code From Last Activate // Operations public: void Activate( void ); // Externally Activate The Link DWORD ModifyLCStyle( DWORD dwRemove, DWORD dwAdd ); // Modify Control's Styles
void SetTarget( LPCTSTR cpTarget )
void SetTarget( const SHELLEXECUTEINFO &seiSEI )
These functions set the Target of the link or the "action to be taken place when the link is Activated". The first version of the function takes a string in a URL format. No validation is done on the string (this allows you to have your own internal format/actions). The second version of the function takes a
SHELLEXECUTEINFO
object. When the second version is used, the contents of the passedSHELLEXECUTEINFO
object are passed the toShellExecuteEx(...)
function. This allows you to have the link do just about anything you want when it is Activated.
void SetDisplay( LPCTSTR cpDisplay )
This function sets the Display string of the link. This is the string that is shown to the user.
void SetToolTip( LPCTSTR cpDisplay )
This function sets the ToolTip for the link.
void SetNormalFont( const LOGFONT &lfNormalFont )
This function sets the "normal" font for the control. The font can be changed at any time.
void SetULFont( const LOGFONT &lfULFont )
This function sets the "underline" font for the control. The font can be changed at any time.
DWORD
GetLastActivateError(
void )
This function returns the error code (if any) from the last time the control was Activated.
void Activate( void )
This function gives the developer a way to externally Activate the link.
DWORD ModifyLCStyle( DWORD dwRemove, DWORD dwAdd
)
This function allows the developer to modify the style bits of the control. It is obviously modeled after a
CWnd
function of a similar name.
History
Version 1.0 Initial public release to
The Code Project. (~12/30/02)
Version 1.1 Double-click bug fix and
additional features. (~1/13/03)