軽量で高速なハイパーリンクコントロールを実現するC++クラスはじめに本稿では、Win32 APIベースのプログラムとMFCプログラムのどちらでも使える、コンパクトで効率的なクラスを紹介します。 ![]() ハイパーリンクコントロールを自作するにあたり、既存のハイパーリンクコントロールをネットでいろいろ探してみましたが、私の求める条件を十分に満たすものは1つもありませんでした。余分な機能が詰め込まれている、バグがある、シンプルだけれども1つか2つ必要な機能が足りない、という具合でした。その中で最も優れていたのが、Neal Stublenのコードでした。とりわけ解決方法のエレガンスさには感銘を受けました。このコードはWin32 APIプログラムでもMFCプログラムでも使えるのです。ただ、残念なことに、バグが1つあるのと、私が求める機能が3つ欠けていました。そこで、Neal Stublenのコードを基にして独自のハイパーリンクコントロールを作成することにしました。本稿では、ハイパーリンクに関してMicrosoftが提供しているオプションについて述べたうえで、Nealが実装した機能と私が追加した機能、それにNealのコードに対するバグ修正とその他いくつかの改善点について説明します。 Microsoftが提供するオプション本稿を初めて発表したとき、数名の読者から、その機能ならWTLが提供している、という指摘をいただきました。今振り返ってみると、それでもなお自分でクラスを書いたのは正しい判断だったと思います。WTLが私の解決策よりも優れている点は、内容が充実していてより多くの機能を備えていることです。しかし、WTLを利用する人の目的は小さくて高速な実行ファイルを作成することのはずですから、もし彼らが必要とする機能が私のクラスにすべて含まれているならば、WTLのクラスよりも私のクラスを使った方がメリットがあると思います。実際、私のクラスと比べた場合、WTLの実装には次のような難点があります。
私は、WTLを使ってプログラムを書いたことはありませんが、仮にその経験があったとしても、きっと本稿の自作クラスを利用するでしょう。Microsoftは、ハイパーリンクコントロールを求める人々のために、もう1つ選択肢を用意しています。「ComCtrl32.dll」のバージョン6にハイパーリンクコントロールを追加したのです。しかし、このバージョンはWindows XPでしか利用できません。他のWindowsオペレーティングシステムでのみ実行可能なアプリケーションでは、新たに用意されたこの一般的なハイパーリンクコントロールライブラリに頼ることはできませんし、そもそも「ComCtl32.dll」のバージョン6でしか使用できない機能を使うべきではありません。潜在的ユーザ層をWindows XPユーザに限定できる場合は問題ないかもしれませんが、そうでない場合の方が多いはずです。 私が作成したクラスの機能まずは、静的コントロールをハイパーリンクにするために必要な変更のうち、すでにNealが解決していたものを紹介しましょう。
以下は、私が追加した機能です。
これらの新機能をどのように実装したかを説明する前に、Nealのコードに加えた大きな構造的な変更について述べておきたいと思います。具体的には、Nealのコードを 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: // Hyperlink colors static COLORREF g_crLinkColor, g_crVisitedColor; 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); }; この変更を行った理由は次のとおりです。
理由1に挙げたカスタマイズを行うには、 このクラスでは、別の点でも改善が行われています。メンバの一部がstatic宣言されていることに気づいたでしょうか。これによって、複数のハイパーリンクコントロールで同じリソース(ハンドカーソルやアンダーライン付きフォントなど)を共有することができます。
if( g_counter++ == 0 )
{
createGlobalResources();
}
また、コントロールウィンドウプロシージャ内の
if( --CHyperLink::g_counter <= 0 )
{
destroyGlobalResources();
}
最初の case WM_SETCURSOR: { SetCursor(CHyperLink::g_hLinkCursor); return TRUE; } それでは、新しい機能の説明に戻りましょう。最も簡単なのは、訪問済みのハイパーリンクの表示色を変更するという機能です。これは 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 WINXDISPLAY(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; } キーボード操作をサポートするには、次のメッセージを処理しなければなりません。
このハイパーリンクコントロールは、スペースキーの押下に反応します。 ここでもう1つ説明しておきたいのは、公開されているハイパーリンクコントロールの多くは
いずれにせよ、関連部分のコードは次のようになります。 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; } 今度は、バグ修正に取り組みましょう。このコントロールでは、 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; } ウィンドウプロシージャの話題を終えるにあたって、もう1つ大事な点に触れておきます。処理されたメッセージは、静的コントロールのプロシージャには戻されません(静的コントロールには必要ないからです)。基本的にはこれでうまく動作しますが、その静的コントロールがすでにサブクラス化されている場合は問題が起こり得るので注意が必要です。たとえば、静的コントロールが、マウスメッセージを処理する必要のあるツールチップコントロールによってサブクラス化されているとします。この場合、ツールチップコントロールは期待どおりには動作しません。ツールチップコントロールを 最後に、 /* * 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 ((LPCTSTR)(DWORD)ga.atom) #define PROP_ORIGINAL_PROC ((LPCTSTR)(DWORD)ga.atom) デモプログラム ここで紹介するデモは、MFC AppWizardで生成した簡単なアプリケーションです。私が作成した まず、ダイアログエディタにいくつかの静的コントロールを追加します。TABSTOPスタイルを選択し、各コントロールにユニークなIDを与えます。また、 class CDemoLink : public CHyperLink { protected: virtual void OnSelect(void) { ((CFrameWnd *)AfxGetMainWnd())->SetMessageText(m_strURL); } virtual void OnDeselect(void) { ((CFrameWnd *)AfxGetMainWnd())-> SetMessageText(AFX_IDS_IDLEMESSAGE); } }; 次に、このダイアログクラスに void CAboutDlg::setURL(CHyperLink &ctr, int id) { TCHAR buffer[128]; int nLen = ::LoadString(AfxGetResourceHandle(), id, buffer, 128); if( !nLen ) { lstrcpy( buffer, __TEXT("")); } ctr.ConvertStaticToHyperlink(GetSafeHwnd(),id,buffer); } BOOL CAboutDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here setURL(m_DemoLink,IDC_HOMEPAGE); setURL(m_DemoMail,IDC_EMAIL); return TRUE; // return TRUE unless you set the focus // to a control // EXCEPTION: OCX Property Pages should // return FALSE } このデモプログラムは、
BOOL CAboutDlgWithToolTipURL::OnInitDialog()
{
CAboutDlg::OnInitDialog();
// TODO: Add extra initialization here
m_ctlTT.Create(this);
setURL(m_DemoLink,IDC_HOMEPAGE);
setURL(m_DemoMail,IDC_EMAIL);
/*
* It is OK to add a Window tool to the tool tip
* control with the CHyperLink dynamically allocated
* URL string because the windows are destroyed with
* WM_DESTROY before the CHyperLink destructor where
* the URL string is freed.
*/
m_ctlTT.AddWindowTool(GetDlgItem(IDC_HOMEPAGE)
->GetSafeHwnd(),
(LPTSTR)m_DemoLink.getURL());
m_ctlTT.AddWindowTool(GetDlgItem(IDC_EMAIL)->GetSafeHwnd(),
(LPTSTR)m_DemoMail.getURL());
return TRUE; // return TRUE unless you set the focus
// to a control
// EXCEPTION: OCX Property Pages should
// return FALSE
}
class CSubclassToolTipCtrl : public CToolTipCtrl { // Construction public: CSubclassToolTipCtrl(); // Operations public: /********************************************************* * * Name : AddWindowTool * * Purpose : Add a window tool by using the Tooltip * subclass feature * * Parameters: * hWin (HWND) Tool window * pszText (LPCTSTR) Tip text (can also be * a string resource ID). * * Return value : Returns TRUE if successful, * or FALSE otherwise. * *********************************************************/ BOOL AddWindowTool( HWND hWin, LPTSTR pszText ); // Implementation public: virtual ~CSubclassToolTipCtrl(); }; /* * Function CSubclassToolTipCtrl::AddWindowTool */ BOOL CSubclassToolTipCtrl::AddWindowTool( HWND hWin, LPTSTR pszText ) { TOOLINFO ti; ::ZeroMemory(&ti, sizeof(TOOLINFO)); ti.cbSize = sizeof(TOOLINFO); ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS; ti.hwnd = ::GetParent(hWin); ti.uId = (UINT)hWin; ti.hinst = AfxGetInstanceHandle(); ti.lpszText = pszText; return (BOOL)SendMessage(TTM_ADDTOOL,0,(LPARAM)&ti); } ここでは、『Programming Windows with MFC』の解決策にちょっとした改善を加えてあります。 まとめ説明は以上です。本稿が皆さんの参考になれば幸いです。本稿がお役に立ったようでしたら、少しだけ時間をとって評価をしていただけると助かります。評価は、本稿の一番下の部分から行えます。 参考文献
改訂履歴2005年12月3日
2005年11月17日
著者紹介lano1106(lano1106)
電気通信業界にて組み込みプロトコルスタックなどに携わる。米連邦航空局(FAA)の次世代の洋上航空交通管理(ATM)システム開発では(冗談のようだが)ATMプロトコルを担当。UNIXという歴史の深いテクノロジーを用いる環境に倦怠感を覚え、現在はフリーランスの開発者としてWindowsを対象としたC/C++開発に従事。お問い合わせは本人のWebサイトまで。
|