BOOKS i'm reading | Small C++ class to transform any static control into a hyperlink control for WindowsContentsIntroductionThis small and efficient C++ class is compatible with Win32 API programs and MFC programs as well.
Prior to developing my own hyperlink control C++ implementation for Windows, I have been looking around on the net in search of already written hyperlink controls for Windows in C/C++. There is a lot around but none were good enough for my standards. Some are bloated with features that I do not need and want. Some are plagued with bugs. Some are simple but lack one or two features I wished to see. The best that I have found is the excellent code written by Neal Stublen. I have been inspired by the elegance that his solution offers which can be used with Win32 API programs and MFC programs as well. Unfortunately, it has a bug and misses three features I was looking for. For this reason, I decided to write my own hyperlink control C++ code based on Neal Stublen's work. In this C++ Windows programming tutorial, I will mention hyperlink options that Microsoft offers, describe the features Neal did put in his code, the features that I added, the bug fix I propose to Neal's code and some other improvements that I have added.
Microsoft optionsAfter the initial release of this article, some readers pointed out that WTL is offering exactly what I was looking for. In retrospect, I still think that it was the right decision to write my own class. One benefit of WTL over my solution is that it is more complete and has more features. However, since WTL user's goal is to create small and fast executable files, they would benefit from using my class over the WTL one *if* my class provides all the features they need. In some aspects, WTL implementation has some drawbacks that my class doesn't have:
I have never used WTL to write a program but if I would, I would use the class described in this article. Microsoft proposes another option for people looking for hyperlink controls. It added such a control to comctrl32.dll version 6 but this version is available only on XP and Microsoft states. An application that has to run on other Windows operating systems cannot rely on the new common control library being present and should not use the features that are available only in ComCtl32.dll, version 6. It might pose a problem if you don't want to restrict your potential user base strictly to this target. FeaturesFirst, the changes needed to a static control to become a hyperlink control that Neal addressed are:
The features that I added are:
Before describing how the new features have been implemented, let me introduce you to the major architectural change that the code underwent. I placed the code into a class. Here is the class definition: class CHyperLink { public: CHyperLink(void); virtual ~CHyperLink(void); BOOL ConvertStaticToHyperlink(HWND hwndCtl, LPCTSTR strURL); BOOL ConvertStaticToHyperlink(HWND hwndParent, UINT uiCtlId, LPCTSTR strURL); BOOL setURL( LPCTSTR strURL); LPCTSTR getURL(void) const { return m_strURL; } protected: /* * Override if you want to perform some action when the link has the focus * or when the cursor is over the link such as displaying the URL somewhere. */ virtual void OnSelect(void) {} virtual void OnDeselect(void) {} LPTSTR m_strURL; // hyperlink URL private: static COLORREF g_crLinkColor, g_crVisitedColor;// Hyperlink colors static HCURSOR g_hLinkCursor; // Cursor for hyperlink static HFONT g_UnderlineFont; // Font for underline display static int g_counter; // Global resources user counter BOOL m_bOverControl; // cursor over control? BOOL m_bVisited; // Has it been visited? HFONT m_StdFont; // Standard font WNDPROC m_pfnOrigCtlProc; void createUnderlineFont(void); static void createLinkCursor(void); void createGlobalResources(void) { createUnderlineFont(); createLinkCursor(); } static void destroyGlobalResources(void) { /* * No need to call DestroyCursor() for cursors acquired through * LoadCursor(). */ g_hLinkCursor = NULL; DeleteObject(g_UnderlineFont); g_UnderlineFont = NULL; } void Navigate(void); static void DrawFocusRect(HWND hwnd); static LRESULT CALLBACK _HyperlinkParentProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK _HyperlinkProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); }; The reasons that motivated this change are:
#1 can be achieved by overriding the This brought me to introduce another improvement. Have you noticed that some members are if( g_counter++ == 0 ) { createGlobalResources(); } And this code has been added to the if( --CHyperLink::g_counter <= 0 ) { destroyGlobalResources(); } To the first case WM_SETCURSOR: { SetCursor(CHyperLink::g_hLinkCursor); return TRUE; } Now let's get back to the new features, the simplest one is to change the color of the hyperlink
control when it is visited. A very simple change to the inline void CHyperLink::Navigate(void) { SHELLEXECUTEINFO sei; ::ZeroMemory(&sei,sizeof(SHELLEXECUTEINFO)); sei.cbSize = sizeof( SHELLEXECUTEINFO ); // Set Size sei.lpVerb = TEXT( "open" ); // Set Verb sei.lpFile = m_strURL; // Set Target To Open sei.nShow = SW_SHOWNORMAL; // Show Normal LASTERRORDISPLAYR(ShellExecuteEx(&sei)); m_bVisited = TRUE; } case WM_CTLCOLORSTATIC: { HDC hdc = (HDC) wParam; HWND hwndCtl = (HWND) lParam; CHyperLink *pHyperLink = (CHyperLink *)GetProp(hwndCtl, PROP_OBJECT_PTR); if(pHyperLink) { LRESULT lr = CallWindowProc(pfnOrigProc, hwnd, message, wParam, lParam); if (!pHyperLink->m_bVisited) { // This is the most common case for static branch prediction // optimization SetTextColor(hdc, CHyperLink::g_crLinkColor); } else { SetTextColor(hdc, CHyperLink::g_crVisitedColor); } return lr; } break; } Now to support keyboard, the following messages must be handled:
The hyperlink control will respond to a space key press. Another point of interest is why choosing
Anyway, here is the relevant code: inline void CHyperLink::DrawFocusRect(HWND hwnd) { HWND hwndParent = ::GetParent(hwnd); if( hwndParent ) { // calculate where to draw focus rectangle, in screen coords RECT rc; GetWindowRect(hwnd, &rc); INFLATERECT(&rc,1,1); // add one pixel all around // convert to parent window client coords ::ScreenToClient(hwndParent, (LPPOINT)&rc); ::ScreenToClient(hwndParent, ((LPPOINT)&rc)+1); HDC dcParent = GetDC(hwndParent); // parent window's DC ::DrawFocusRect(dcParent, &rc); // draw it! ReleaseDC(hwndParent,dcParent); } } case WM_KEYUP: { if( wParam != VK_SPACE ) { break; } } // Fall through case WM_LBUTTONUP: { pHyperLink->Navigate(); return 0; } case WM_SETFOCUS: // Fall through case WM_KILLFOCUS: { if( message == WM_SETFOCUS ) { pHyperLink->OnSelect(); } else // WM_KILLFOCUS { pHyperLink->OnDeselect(); } CHyperLink::DrawFocusRect(hwnd); return 0; } Have you noticed that both Now, let's attack the bug fix. The control can lose the mouse capture in others ways than calling case WM_MOUSEMOVE: { if ( pHyperLink->m_bOverControl ) { // This is the most common case for static branch prediction // optimization RECT rect; GetClientRect(hwnd,&rect); POINT pt = { LOWORD(lParam), HIWORD(lParam) }; if (!PTINRECT(&rect,pt)) { ReleaseCapture(); } } else { pHyperLink->m_bOverControl = TRUE; SendMessage(hwnd, WM_SETFONT, (WPARAM)CHyperLink::g_UnderlineFont, FALSE); InvalidateRect(hwnd, NULL, FALSE); pHyperLink->OnSelect(); SetCapture(hwnd); } return 0; } case WM_CAPTURECHANGED: { pHyperLink->m_bOverControl = FALSE; pHyperLink->OnDeselect(); SendMessage(hwnd, WM_SETFONT, (WPARAM)pHyperLink->m_StdFont, FALSE); InvalidateRect(hwnd, NULL, FALSE); return 0; } To complete the window procedures topic, there is an important detail that needs to be highlighted. The processed messages are not passed back to the static control procedure because the static control does not need them. It does work fine but be aware that it could cause some problems if the static control is already subclassed. Consider the example where the static control would be already subclassed with the Tooltip control that needs to process mouse messages. In that situation, the Tooltip control would not work as expected. In the demo program section, I will show how you can use the Tooltip control with And finally, to speed the /* * typedefs */ class CGlobalAtom { public: CGlobalAtom(void) { atom = GlobalAddAtom(TEXT("_Hyperlink_Object_Pointer_") TEXT("\\{AFEED740-CC6D-47c5-831D-9848FD916EEF}")); } ~CGlobalAtom(void) { DeleteAtom(atom); } ATOM atom; }; /* * Local variables */ static CGlobalAtom ga; #define PROP_OBJECT_PTR MAKEINTATOM(ga.atom) #define PROP_ORIGINAL_PROC MAKEINTATOM(ga.atom) Page 1 2 |