|

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;
const UINT NMLC_SETFOCUS = NM_SETFOCUS;
const UINT NMLC_KILLFOCUS = NM_KILLFOCUS;
const UINT NMLC_RCLICK = NM_RCLICK;
const UINT NMLC_RDBLCLICK = NM_RDBLCLICK;
const UINT NMLC_ACTIVATE = ( NM_FIRST - 90 );
const UINT NMLC_MOUSEENTER = ( NM_FIRST - 89 );
const UINT NMLC_MOUSEHOVER = ( NM_FIRST - 88 );
const UINT NMLC_MOUSELEAVE = ( NM_FIRST - 87 );
const UINT NMLC_BEGINDRAG = ( NM_FIRST - 86 );
const UINT NMLC_ENDDRAG = ( NM_FIRST - 85 );
const UINT NMLC_BEGINRDRAG = ( NM_FIRST - 84 );
const UINT NMLC_ENDRDRAG = ( NM_FIRST - 83 );
const UINT NMLC_MCLICK = ( NM_FIRST - 82 );
const UINT NMLC_MDBLCLICK = ( NM_FIRST - 81 );
struct NMLINKCTRL
{
NMHDR m_hdr;
DWORD m_dwRetVal;
DWORD m_dwFlags;
POINT m_ptWhere;
};
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;
const DWORD LCF_LCTRL = 0x00000002;
const DWORD LCF_RSHIFT = 0x00000004;
const DWORD LCF_LSHIFT = 0x00000008;
const DWORD LCF_RALT = 0x00000010;
const DWORD LCF_LALT = 0x00000020;
const DWORD LCF_CTRL = 0x00000040;
const DWORD LCF_SHIFT = 0x00000080;
const DWORD LCF_ALT = 0x00000100;
Next, let us look
at the styles supported by the control:
const DWORD LCS_HOTTRACK = 0x00000001;
const DWORD LCS_DRAGDROP = 0x00000002;
const DWORD LCS_VISITED = 0x00000004;
const DWORD LCS_TOOLTIPS = 0x00000010;
|
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:
public:
void SetTarget( LPCTSTR cpTarget );
void SetTarget( const SHELLEXECUTEINFO &seiSEI );
void SetDisplay( LPCTSTR cpDisplay );
void SetToolTip( LPCTSTR cpToolTip );
void SetNormalFont( const LOGFONT &lfNormalFont );
void SetULFont( const LOGFONT &lfULFont );
DWORD GetLastActivateError( void );
public:
void Activate( void );
DWORD ModifyLCStyle( DWORD dwRemove, DWORD dwAdd );
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 passed SHELLEXECUTEINFO object are
passed the to ShellExecuteEx(...) 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)
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 44 (Total in Forum: 44) (Refresh) | FirstPrevNext |
|
|
 |
|
|
Thank-you! very nice for an "about box" Great article.
A train station is where the train stops. A bus station is where the bus stops. On my desk, I have a work station.... _______________________________________________________________________________________ My programs never have bugs, they just develop random features.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I just started using the CJRTSLinkCtrl class, but I'm experiencing some memory corruption problems. When I click the link, I sometimes get the following message from Visual Studio 2005 (SP1):
Windows has triggered a breakpoint in XX.exe. This may be due to a corruption of the heap, and indicates a bug in XX.exe or any of the DLLs it has loaded. The output window may have more diagnostic information
It doesn't happen every time, but quite often. This problem only surfaces when using the link control. (I'm currently using it on a modal property sheet, if that makes any difference.)
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Well, truth be told, this control has been around for some time, as had VS 2005 and this is the first report I have regarding heap corruption with it (as noted by the threads present).
Not to cast stones, but could you provide the exact code that you are using to create, configure, and respond to its events.
Thanks!
Peace!
-=- James Please rate this message - let me know if I helped or not!
If you think it costs a lot to do it right, just wait until you find out how much it costs to do it wrong! Avoid driving a vehicle taller than you and remember that Professional Driver on Closed Course does not mean your Dumb Ass on a Public Road! See DeleteFXPFiles
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Wow, that was a fast reply 
I, too, initially suspected that it had to be my own code since I couldn't find a single incident of heap corruption in the control's lifetime. I'm currently attempting to reproduce the behavior in another project. I'll keep you posted.
At this point, I'm not handling or using any of the events that the control emits. I've just added the control to a dialog and am calling SetDisplay(), SetTarget(), and SetToolTip() in OnInitDialog(). That's it. I'm doing nothing else with the control.
Cheers,
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
So, I just put the control on the first dialog I have in my application and clicked the link once. The URL was opened correctly but subsequent clicks had no effect, and when I took a look at the Output window in VS I got heaps of the following exceptions:
First-chance exception at 0x77127b04 in XX.exe: 0xC0000005: Access violation reading location 0xfffffff8.
I tried a second time, and this time around I was able to click the link at least 10 times before I got the following error:
First-chance exception at 0x75e3e710 in XX.exe: 0xC0000005: Access violation reading location 0x00000008. Unhandled exception at 0x75e3e710 in XX.exe: 0xC0000005: Access violation reading location 0x00000008.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
OK - thanks to Halloko sending me a test app that reproduces the problem, I have discovered the issue. There is a memory allocation routine that does not consider character-width differences and in cases of Unicode builds will step over the bounds of an allocated block of memory. And it steps over it a bunch: ~4KB worth!
On line 574 there is a memory allocation call that uses ::GlobalAlloc(...) at it should be changed as shown below:HGLOBAL hGlobal = ::GlobalAlloc( GMEM_SHARE |
// GMEM_ZEROINIT ), 4096 + 1 );// Allocate Memory For Target GMEM_ZEROINIT ), ( ( 4096 + 1 ) * sizeof( TCHAR ) ) ); // Allocate Memory For Target That should correct the issue.
I am not sure when I will have the code archive updated but I will try to get it done this weekend.
Thanks again to Halloko for helping me track this down!
Peace!
-=- James Please rate this message - let me know if I helped or not!
If you think it costs a lot to do it right, just wait until you find out how much it costs to do it wrong! Avoid driving a vehicle taller than you and remember that Professional Driver on Closed Course does not mean your Dumb Ass on a Public Road! See DeleteFXPFiles
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
It would be really nice to have a SizeToContent() in your class - can you make one? Also I feel that a const CString& GetDisplay() are missing in your class.
Do you think you can provide at least a SizeToContent()? My first naive try did'nt work:
// Size to content: TEST. CJRTSLinkCtrl *pCtrl = GetPtr2URLCtrl_Link2Webpage(0); CDC dc; dc.Attach(pCtrl->GetDC()->m_hDC); dc.SelectObject(&m_fGUIBold); // Just my test-font - not the real one. CString cstrText(_T("www.proxy4free.com")); //pCtrl->GetDisplay(cstrText); CSizeEx isTextExtent(dc.GetTextExtent(cstrText, cstrText.GetLength())); dc.DeleteDC();
CRectEx irWindow(::GetWindowRectEx(pCtrl)); pCtrl->SetWindowPos(&CWnd::wndTop, irWindow.left, irWindow.top, isTextExtent.cx, irWindow.Height(), 0);
It may have been on the wrong time or spot I tried...
Regards, Michael Mogensen, mm it-consult dk.
><((((º> ·.¸¸.· ><((((º> ·.¸¸.· ><((((º>
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hello James,
First of all, thanks for developing such a good Hyper Link control. This is the best control. I have used it and its working very well. But my problem is that, I have a facility of Print and Print Preview in my tool, but as CJRTSLinkCtrl is dependent on Button, I cannot see the content (Display String) of the button in my Print Preview window. Let me know how to make this control (or its content string) printable in the Print Preview and finally in the Print.
Any help will be appreciated.
Thanks,
Nitin Dangare
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thank you for your comments on the control.
To be able to assist you further, I would need a better idea of how the control is being used. For example, are you using it on a CFormView-based view and then using Print Preview on the view?
Peace!
-=- James (Sonork:100.21837) [Tip for SUV winter driving survival: "Professional Driver on Closed Course" does not mean "your Dumb Ass on a Public Road"!] [Get Delete FXP Files Now!]
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Thanks for your quick and prompt reply.
My application is a MDI. I have a class xxxview that is inherited from CDaoRecordView, futhur I have a class yyyView inherited from xxxview. I also have zzzView inherited from yyyView. Visually this means CDaoRecordView->xxxview->yyyView->zzzView. I have a dialog box that will display the records using the zzzView. This implies that the dialog box is placed on zzzView. The record set contains 2 fields that need hyper linking, hence I used your class for these fields. Navigation from one record to other, paints the hyper linked fields very well. I need to print or print preview this (dialog) view. But due to the buttons used for this hyper linking, I am unable to print or print preview these 2 specific control/fields, rest all fields get printed in the print preview.
Any furthur help is really appreciated.
Thanks,
Nitin Dangare
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I expect that this is a naive question, but I can't get this control to work in a non-dialog window derived from CWnd. In particular, I can't figure out how to use the control's Create method. Is there detailed documentation for this method? Thanks!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The easiest thing that I can suggest is to create a normal Button control where you want the link to be, and then subclass it using the Link Control, just like how it normally happens via Classwizard.
The Create method remains unchanged from the base class' version.
Peace!
-=- James (Sonork:100.21837) [Tip for SUV drivers: "Professional Driver on Closed Course" does not mean "your Dumb Ass on a Public Road"!] [Get Check Favorites 1.6 Now!]
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Your control doesnt paint properly in Windows XP, or any visual style where the color of a button face (m_crBackground, COLOR_BTNFACE) is not the same as the color of dialog box (COLOR_3DFACE), or even worse the color of a property sheet background (COLOR_.... I dont know, they dont have an index for it! Its white-ish!).
It paints a grey box around the text... looks bad.
I fixed it by replacing
dcDC.SetBkColor( m_crBackground ); dcDC.ExtTextOut( iSpaceX, iSpaceY, ETO_OPAQUE, &rClient, m_sDisplay, NULL );
with
dcDC.SetBkMode(TRANSPARENT); dcDC.ExtTextOut( iSpaceX, iSpaceY, 0, &rClient, m_sDisplay, NULL );
But, this causes the focus rect to flash various colors when I move the mouse over the control, since it is XORing with what was there before (no longer a solid color). I just commented out DrawFocusRect cause I dont care about it.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The article information at the top of the page includes WindowsXP, and it should not -- WindowsXP includes a native HyperLink control in its common controls. Since you already have a native control, you should not be using a replacement like mine.
Turning off styles (if using them), and using Win2K-style controls might help the drawing issue, though.
I will investigate if using COLOR_3DFACE is a more appropriate color index to use. Now that I think about it, it likely is. As for the Property Sheet background... Well, as I said, you should not be using my control on XP, anyway! 
Peace!
-=- James (Sonork:100.21837) [Tip for SUV winter driving survival: "Professional Driver on Closed Course" does not mean "your Dumb Ass on a Public Road"!] [Get Check Favorites 1.5 Now!]
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
So if I use this control, customers who run my program under XP will see problems? Am I understanding this correctly? That's a showstopper.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Since there are already at least three Microsoft Knowledge Base articles regarding Themes, I would suggest trying it without themes enabled; I built the project on XP, and experienced no trouble with it (but I do not use themes).
As mentioned above, Windows XP already has a native link control, so when running on XP, your application should use it instead of mine; why reinvent that wheel?
At some point, I will "code around" the problem. Just not right now.
Peace!
-=- James (Sonork:100.21837) [Tip for SUV winter driving survival: "Professional Driver on Closed Course" does not mean "your Dumb Ass on a Public Road"!] [Get Delete FXP Files Now!]
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
James R. Twine wrote: As mentioned above, Windows XP already has a native link control, so when running on XP, your application should use it instead of mine; why reinvent that wheel?
I think this is not a good point of view, it is not practical for a coder to choose at runtime a control or another depending on operating system. A good control must consider all cases. If your control doesn't work correctly with WinXP themes then you have 2 alternatives, I think: 1) Make some special arrangements to detect if theme is active to make a different painting. 2) Wrap the WinXP control inside your control and activate it in case a theme is active, this way user will have a UNIQUE interface to use both alternatives.
Best regards, Jaime.
|
| Sign In·View Thread·PermaLink | 4.50/5 (2 votes) |
|
|
|
 |
|
|
Your point is valid, accepting the fact that when I first wrote the control, I did not know about Windows XP having a native one, nor did I even have it installed at that point. All I knew was that the implementations already out there were not entirely correct.
To your point: if I had reason to believe that it was being used by enough developers, I would consider releasing my internal updates to it (same goes for the other controls I have written). But most of the links I see in applications are still based the static control, and I have never seen my name in the credits or even received an email regarding use of my code. If I do release any updates, or new code, they will be released under my FSCL (Free Source Code License, http://www.jrtwine.com/fscl.htm[^]). Since there is no 100% clear license term for code submitted to CP, they will likely only be available from my web site.
Thank you for the feedback!
BTW... Using COLOR_3DFACE solves the background color issue. In the constructor, replace line:
m_crBackground = ::GetSysColor( COLOR_BTNFACE );
With:
m_crBackground = ::GetSysColor( COLOR_3DFACE );
Changes to adjust the "active rect" for the link itself are not included here.
Peace!
-=- James If you think it costs a lot to do it right, just wait until you find out how much it costs to do it wrong! Tip for new SUV drivers: Professional Driver on Closed Course does not mean your Dumb Ass on a Public Road! DeleteFXPFiles & CheckFavorites (Please rate this post!)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Well, I have tested your control in a simple dialog in WinXP with silver and olive colored themes and seems to work Ok. So, at least I think it works ok with standard dialogs.
BR Jaime.
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Problem 1:
m_crHotTrack = ::GetSysColor( COLOR_HOTLIGHT ); //RGB( 0x00, 0x00, 0xFF );
COLOR_HOTLIGHT is defined if WINVER >= 0x0500
What if WINVER < 0x0500 (Win98, ...).
Problem 2:
OCR_NORMAL is available only with # define OEMRESOURCE
I'm using VC6.0
Am I wrong?
Predrag Manojlovic
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
| | |