在很多应用程序中都提供了动态改变光标的功能,例如,对于我们常用的Word字处理软件,当在文档中输入邮件地址后,每当鼠标进入邮件地址区域,鼠标的形状就由箭头改变为手指形状。本实例在一个基于对话框的程序中模拟上述功能,如果将鼠标移到对话框的按钮上(" OK "键和"Cancel"键),光标指针变为蓝色手指,如果将鼠标指针移到带下划线的超链接上,光标指针又变为另外一个Windows中常常使用的手指。
一、实现方法
Wndows编程中有两种方法改变光标,一种方法是当应用的主窗口类注册时,为WNDCLASS结构提供一个全程光标指针(HCURSOR),另外一种方法是在程序中处理WM_SETCURSOR消息来设置鼠标光标。标准的MFC应用程序使用第一种方法自动在主窗口注册时将光标指针设置为一个箭头。如果要改变光标指针,则可以通过在主窗口或 子窗口 中重载消息WM_SETCURSOR的处理函数来重新设置鼠标指针。下面的代码实现更改按钮上的光标:
| // handle WM_SETCURSOR in button class BOOL CMyButton::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT msg) { ::SetCursor(m_hMyCursor); return TRUE; } |
当用户将鼠标指针移到按钮上时,鼠标不被捕获,Windows发送一个WM_SETCURSOR消息到按钮。从上面OnSetCursor的代码中可以看到,它传递的第一个参数是窗口句柄(pWnd)-即鼠标指针所指的窗口,这里指的是按钮本身;OnSetCursor传递的第二个参数是nHitTest,这是一个鼠标点击测试代码(Mouse Hit -Test Codes),它以HTXXX开头,用于WM_NCHITTEST消息;OnSetCursor传递的第三个参数是触发事件的鼠标消息,例如它的值可以为WM_MOUSEMOVE。WM_SETCURSOR是专门用来设置鼠标指针的消息,当设置了鼠标指针以后,应该让它返回TRUE以防止Windows再作缺省处理。
WM_SETCURSOR的处理机制是这样的,如果有父窗口的话,缺省的窗口过程首先发送WM_SETCURSOR消息到父窗口,如果父窗口处理WM_SETCURSOR消息(即返回TRUE),则Windows不再作任何多余的事情,处理完消息便结束。如果父窗口不处理WM_SETCURSOR消息(即返回FALSE),Windows让子窗口来处理WM_SETCURSOR,如果子窗口也不做任何处理(返回FALSE),Windows使用全程光标指针,如果没有全程光标指针,则使用箭头指针。
如果你在程序中要是使用动态光标指针,你必须决定是在子窗口处理WM_SETCURSOR消息还是在父窗口中处理WM_SETCURSOR消息。两种方法各有利弊,根据具体情况而定。一般总是让对象决定它们自己的行为属性-也就是说最好在子窗口中做处理。本例中的子窗口即静态控件。这就要派生一个新的CStatic类的子类,该类有自己的消息映射及其消息处理过程。
CWnd::SetCursor()函数使用起来比较简单,只要将待选择的鼠标的句柄(装载鼠标函数为:LoadIcon())作为该函数的参数就可以了。
二、编程步骤
1、 启动Visual C++ 6.0,生成一个基于对话框的应用程序MyCursor,并添加光标资源IDC_MYHAND;
2、 使用Class Wizard添加CHyperlink、StatLink类定义;
3、 在对话框类中定义下列变量HICON m_hIcon、 CStaticLink m_wndLink1、CStaticLink m_wndLink2、HCURSOR m_hButtonCursor;
4、 使用Class Wizard为对话框类添加WM_SETCURSOR消息处理函数;
5、 添加代码,编译运行程序。
三、程序代码
| //////////////////////////////////////////////////////////////////////////// #ifndef _HYPRILNK_H #define _HYPRILNK_H // Simple text hyperlink derived from CString class CHyperlink : public CString { public: CHyperlink(LPCTSTR lpLink = NULL) : CString(lpLink) { } ~CHyperlink() { } const CHyperlink& operator=(LPCTSTR lpsz) { CString::operator=(lpsz); return *this; } operator LPCTSTR() { return CString::operator LPCTSTR(); } virtual HINSTANCE Navigate() { return IsEmpty() ? NULL : ShellExecute(0, _T("open"), *this, 0, 0, SW_SHOWNORMAL); } }; #endif ///////////////////////////////////////////////////////////////////////////////// #ifndef _STATLINK_H #define _STATLINK_H #include "HyprLink.h" class CStaticLink : public CStatic { public: DECLARE_DYNAMIC(CStaticLink) CStaticLink(LPCTSTR lpText = NULL, BOOL bDeleteOnDestroy=FALSE); ~CStaticLink() { } // Hyperlink contains URL/filename. If NULL, I will use the window text. // (GetWindowText) to get the target. CHyperlink m_link; COLORREF m_color; // Default colors you can change // These are global, so they're the same for all links. static COLORREF g_colorUnvisited; static COLORREF g_colorVisited; static HCURSOR g_hCursorLink; protected: CFont m_font; // underline font for text control BOOL m_bDeleteOnDestroy; // delete object when window destroyed? virtual void PostNcDestroy(); // message handlers DECLARE_MESSAGE_MAP() afx_msg UINT OnNcHitTest(CPoint point); afx_msg HBRUSH CtlColor(CDC* pDC, UINT nCtlColor); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); }; #endif _STATLINK_H ///////////////////////////////////////////////////////////////////////// #include "StdAfx.h" #include "StatLink.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif COLORREF CStaticLink::g_colorUnvisited = RGB(0,0,255); // blue COLORREF CStaticLink::g_colorVisited = RGB(128,0,128); // purple HCURSOR CStaticLink::g_hCursorLink = NULL; IMPLEMENT_DYNAMIC(CStaticLink, CStatic) BEGIN_MESSAGE_MAP(CStaticLink, CStatic) ON_WM_NCHITTEST() ON_WM_CTLCOLOR_REFLECT() ON_WM_LBUTTONDOWN() ON_WM_SETCURSOR() END_MESSAGE_MAP() /////////////////// // Constructor sets default colors = blue/purple. // bDeleteOnDestroy is used internally by PixieLib in CPixieDlg. CStaticLink::CStaticLink(LPCTSTR lpText, BOOL bDeleteOnDestroy) { m_link = lpText; // link text (NULL ==> window text) m_color = g_colorUnvisited; // not visited yet m_bDeleteOnDestroy = bDeleteOnDestroy; // delete object with window? } ////////////////// // Normally, a static control does not get mouse events unless it has // SS_NOTIFY. This achieves the same effect as SS_NOTIFY, but it's fewer // lines of code and more reliable than turning on SS_NOTIFY in OnCtlColor // because Windows doesn't send WM_CTLCOLOR to bitmap static controls. // UINT CStaticLink::OnNcHitTest(CPoint point) { return HTCLIENT; } ////////////////// // Handle reflected WM_CTLCOLOR to set custom control color. // For a text control, use visited/unvisited colors and underline font. // For non-text controls, do nothing. Also ensures SS_NOTIFY is on. // HBRUSH CStaticLink::CtlColor(CDC* pDC, UINT nCtlColor) { ASSERT(nCtlColor == CTLCOLOR_STATIC); DWORD dwStyle = GetStyle(); HBRUSH hbr = NULL; if ((dwStyle & 0xFF) <= SS_RIGHT) { // this is a text control: set up font and colors if (!(HFONT)m_font) { // first time init: create font LOGFONT lf; GetFont()->GetObject(sizeof(lf), &lf); lf.lfUnderline = TRUE; m_font.CreateFontIndirect(&lf); } // use underline font and visited/unvisited colors pDC->SelectObject(&m_font); pDC->SetTextColor(m_color); pDC->SetBkMode(TRANSPARENT); // return hollow brush to preserve parent background color hbr = (HBRUSH)::GetStockObject(HOLLOW_BRUSH); } return hbr; } ///////////////// // Handle mouse click: navigate link // void CStaticLink::OnLButtonDown(UINT nFlags, CPoint point) { if (m_link.IsEmpty()) { // no link: try to load from resource string or window text m_link.LoadString(GetDlgCtrlID()) || (GetWindowText(m_link),1); if (m_link.IsEmpty()) return; } // Call ShellExecute to run the file. // For an URL, this means opening it in the browser. // HINSTANCE h = m_link.Navigate(); if ((UINT)h > 32) { // success! m_color = g_colorVisited; // change color Invalidate(); // repaint } else { MessageBeep(0); // unable to execute file! TRACE(_T("*** WARNING: CStaticLink: unable to navigate link %s\n"),(LPCTSTR)m_link); } } ////////////////// // Set "hand" cursor to cue user that this is a link. If app has not set // g_hCursorLink, then try to get the cursor from winhlp32.exe, // resource 106, which is a pointing finger. This is a bit of a kludge, // but it works. // BOOL CStaticLink::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { if (g_hCursorLink == NULL) { static bTriedOnce = FALSE; if (!bTriedOnce) { CString windir; GetWindowsDirectory(windir.GetBuffer(MAX_PATH), MAX_PATH); windir.ReleaseBuffer(); windir += _T("\\winhlp32.exe"); HMODULE hModule = LoadLibrary(windir); if (hModule) { g_hCursorLink = CopyCursor(::LoadCursor(hModule, MAKEINTRESOURCE(106))); } FreeLibrary(hModule); bTriedOnce = TRUE; } } if (g_hCursorLink) { ::SetCursor(g_hCursorLink); return TRUE; } return FALSE; } ////////////////// // Normally, a control class is not destoyed when the window is; // however, CPixieDlg creates static controls with "new" instead of // as class members, so it's convenient to allow the option of destroying // object with window. In applications where you want the object to be // destoyed along with the window, you can call constructor with // bDeleteOnDestroy=TRUE. // void CStaticLink::PostNcDestroy() { if (m_bDeleteOnDestroy) delete this; } ////////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "resource.h" #include "TraceWin.h" #include "StatLink.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif class CMyDialog : public CDialog { public: CMyDialog(CWnd* pParent = NULL); ~CMyDialog(); //protected: private: HICON m_hIcon; CStaticLink m_wndLink1; // web links CStaticLink m_wndLink2; // the button cursor HCURSOR m_hButtonCursor; virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT msg); DECLARE_MESSAGE_MAP() }; BEGIN_MESSAGE_MAP(CMyDialog, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_WM_SETCURSOR() END_MESSAGE_MAP() CMyDialog::CMyDialog(CWnd* pParent /*=NULL*/) : CDialog(IDD_MYDIALOG, pParent) {} CMyDialog::~CMyDialog() {} ////////////////// // Initialize dialog // BOOL CMyDialog::OnInitDialog() { CDialog::OnInitDialog(); // set up hyperlinks m_wndLink1.m_link = _T("http://www.vckbase.com"); m_wndLink2.m_link = _T("mailto:vckbase@public.hk.hi.cn"); m_wndLink1.SubclassDlgItem(IDC_PDURL, this); m_wndLink2.SubclassDlgItem(IDC_MSDNURL, this); // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // load button cursor m_hButtonCursor = AfxGetApp()->LoadCursor(IDC_MYHAND); return TRUE; // return TRUE unless you set the focus to a control } ////////////////// // Set cursor if mouse is over a button. // BOOL CMyDialog::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT msg) { CString sClassName; ::GetClassName(pWnd->GetSafeHwnd(),sClassName.GetBuffer(80),80); if (sClassName=="Button" && m_hButtonCursor) { ::SetCursor(m_hButtonCursor); return TRUE; } return CDialog::OnSetCursor(pWnd, nHitTest, msg); } |
四、小结
本文不仅介绍了动态改变程序中的鼠标形状,还介绍了如何实现静态文本的超连接,读者朋友可以将这部分代码修改后应用到自己开放的应用程序中,例如在程序中添加自己的网页和邮箱地址等,我想这一定是件很令人鼓舞的事情吧。

