如何在MFC设计超链接控件类

来源:互联网 发布:php打印数组的值 编辑:程序博客网 时间:2024/05/28 01:36

关于如何在MFC设计自己的超链接控件类,KC之前写过这样的文章。额,其实那篇文章也就一代码……而且代码质量有点烂

于是经过重修Rebuild之后,把设计的详细过程加以分析~


1.摘要

  控件的超链接效果在程序的UI设计中是经常会碰到的。但是在MFC的低版本中(VS2008所对应的MFC 9之前的版本),并没有现成的控件可以使用(WTL在早期似乎就提供了类似的控件)。

  什么?你说可以在STATIC控件的WM_MOUSEMOVE中修改鼠标指针达到目的?呃,这个想法非常好,这个思路可以马上解决问题。但是呢,呃,问题在于,STATIC控件默认是不接受WM_MOUSEMOVE消息的。

而这也是我们设计类的主要原因,我们可以稍稍动些手脚,至于具体,下面再说~

  如果你需要这一效果,但是不想更新自己的IDE或者转而使用WTL,又或者你希望能够了解如何自己动手实现这一效果,那么此文可以为你提供一些帮助。


2. 我们需要什么?

在设计类CLabelLink之前,我们需要明确我们的需求。这是非常重要的一个过程,在SE中,这个步骤被华丽地称作“需求分析”

1:控件宿主

在现有的控件上进行二次开发是一个不错的选择,这样做可以避免需要自己设计一个ACTX控件的麻烦。

在现有的控件中,静态文本STATIC无疑是最好的选择。所以我们的类,将选择继承自CStatic。并且为了能够接受WM_MOUSEMOVE消息,我们需要添加相应的消息映射接口~

2:一些基础功能

作为超链接控件,我们至少需要能够改变控件的背景颜色和文本颜色。而且控件应该有鼠标指针切换的功能(即鼠标移到控件上,指针变为小手,移除后恢复)

如果你喜欢,可以增加下划线效果(个人不喜欢超链接带下划线TvT)

至于单击事件,是继承自CStatic

3:如何实现

嗯,终于到重点了。

关于如何实现,一两句废话是讲不清楚的,也难以讲清楚,所以我们有必要结合代码来说明。

首先我们要解决类声明的问题(class declaration)

下面CLabelLink的类声明

view source
print?
01#pragma once
02 
03class CLabelLink : public CStatic
04{
05  public:
06    CLabelLink();
07    virtual~CLabelLink();  // 保证正确析构
08    // interface declaration
09    voidSetTextColor(COLORREFclrText);  // 设置文本颜色
10    voidSetBackgroundColor(COLORREFclrBackground);  //设置背景颜色
11    voidEnableTrack(BOOLbEnable = TRUE);  // 是否鼠标跟踪.即鼠标移入控件时指针切换的功能
12    voidSetLinkUrl(LPCTSTRlpszTargetUrl);    // 设置目标链接地址
13 
14  protected:
15    virtualvoid PreSubclassWindow();  // 用以设置控件属性
16    afx_msgHBRUSH CtlColor(CDC *pDC,UINT nCtlColor); // 控件颜色设置消息映射
17    afx_msgvoid OnMouseMove(UINTnFlags, CPoint point);  // 鼠标移动消息映射
18    afx_msgvoid OnStnClicked(); // 控件单击消息映射
19    DECLARE_MESSAGE_MAP()
20 
21  protected:
22    BOOLm_bHover;
23    BOOLm_bTrack;
24    COLORREFm_clrTextColor;
25    COLORREFm_clrBackgroundColor;
26    CBrush m_Brush;
27    CString m_sTargetUrl;
28};

大致了解下上面的代码,我们就会发现,CLabelLink提供给Coder的接口主要就那么四个:SetTextColor、SetBackgroundColor、EnableTrack和SetLinkUrl。对于这四个接口,理解起来都是没有问题的。

首先我们需要关注的是如何设置控件的文本颜色和背景颜色。在MFC中,这通常是利用DialogBox的WM_CTLCOLOR消息中进行控件绘制。

但是,由于我们的CLabelLink是独立于DialogBox存在的,控制控件绘制的函数当然不能够存在于DialogBox的代码中。那么,有什么办法能让控件自己进行绘制呢?

答案是肯定的,在MFC4.0之后,新增加了一种技术叫消息反射(Notification Reflect)。消息反射能够让父窗体收到WM_CTLCOLOR之后把消息“发射”给控件,这样,控件就可以自己处理一些事件

Ps:关于消息反射,这里不再详细阐述,详情请参照:http://msdn.microsoft.com/en-us/library/eeah46xd(VS.71).aspx

下面是绘制控件及反射消息的相关代码

view source
print?
01afx_msg HBRUSHCtlColor(CDC *pDC, UINTnCtlColor);  // in head file
02 
03ON_WM_CTLCOLOR_REFLECT()  // put it in the Message-Map-Declaration
04 
05HBRUSH CLabelLink::CtlColor(CDC *pDC, UINT nCtlColor)
06{
07  pDC->SetTextColor(m_clrTextColor);
08  pDC->SetBkColor(m_clrBackgroundColor);
09 
10  return(HBRUSH)m_Brush;
11}
12 
13/*
14  输  入: clrText(COLORREF) - 要设置的颜色
15  输  出: -
16  功  能: 设置字体颜色参数
17*/
18void CLabelLink::SetTextColor(COLORREF clrText)
19{
20  m_clrTextColor = clrText;
21  // 强制引起重绘
22  Invalidate(TRUE);
23}
24 
25/*
26  输  入: clrBackground(COLORREF) - 要设置的颜色
27  输  出:
28  功  能: 设置背景颜色参数
29*/
30void CLabelLink::SetBackgroundColor(COLORREFclrBackground)
31{
32  m_clrBackgroundColor = clrBackground;
33  Invalidate(TRUE);
34}

这段代码没有问题,但是在使用上是有问题的。

既然开放了设置文本颜色和背景的接口,就意味着Coder能够控制相应的属性,而这些属性是由成员变量控制的。那么,如果我们没有设置这些属性,就直接拿来使用,会发生什么问题?

嗯,似乎会发生传说中不可预期的问题。所以,我们有必要对这些东西进行初始化。

显然,这是构造函数应该干的事情。

view source
print?
01CLabelLink::CLabelLink() : m_bTrack(FALSE), m_bHover(FALSE)
02{
03  m_clrTextColor = RGB(0, 0, 0);
04 
05  // 背景支持
06  // MS COLOR_3DFACE是透明的?
07  m_clrBackgroundColor = ::GetSysColor(COLOR_3DFACE);
08  m_Brush.CreateSolidBrush(m_clrBackgroundColor);
09}
10 
11CLabelLink::~CLabelLink()
12{
13}

如你所见,我们初始化了一些东西,设置了一些东西。而且,我们并没有为析构函数动手,因为我们实在没有什么要做的。

OK,搞定这些准备工作,我们需要为CLabelLink加上MouseTrack的功能。

首先需要为类添加WM_MOUSEMOVE消息映射(利用Wizard和手动添加均可)。

添加完毕后,我们需要整理下思路,大致的流程如下:

显然,我们需要早MouseMove跟踪鼠标位置,这可以用鼠标所在点坐标判断是否在控件的矩形区域内。

view source
print?
01void CLabelLink::OnMouseMove(UINT nFlags, CPoint point)
02{
03  if(m_bTrack)
04  {
05    if(m_bHover)
06    {
07      CRect rec;
08      GetClientRect(&rec);
09 
10      // 鼠标离开了LabelLink
11      if(!rec.PtInRect(point))
12      {
13        m_bHover = FALSE;
14        ::ReleaseCapture();
15      }
16    }
17    // 鼠标进入LabelLink
18    else
19    {
20      m_bHover = TRUE;
21      SetCapture();
22 
23      // #define IDC_HAND  MAKEINTRESOURCE(32649)
24      // 直接引用IDC_HAND需要添加头文件
25      ::SetCursor(AfxGetApp()->LoadStandardCursor(MAKEINTRESOURCE(32649)));
26 
27      Invalidate(TRUE);
28    }
29  }
30 
31  CStatic::OnMouseMove(nFlags, point);
32}

上面的代码比较容易理解,要注意的是几个判断条件

SetCapture是控件的成员函数,作用是强制窗体将所有鼠标消息发送给控件处理而不管鼠标是否在控件可管理的范围内。ReleaseCapture则取消这种行为。

这里有一个问题,使用了SetCursor为什么没有RestorCursor这样相关的函数?

本来是需要的,但是MFC很无聊,他做了很多偷鸡摸狗的事情

If your application must set the cursor while it is in a window, make sure the class cursor for the specified window’s class is set to NULL. If the class cursor is not NULL, the system restores the class cursor each time the mouse is moved.

所以,Restore的工作,我们不用做了。

剩下还有一个函数我们需要重点关照,PreSubclassWindow。

这个函数的作用是允许窗体或控件被子类化前触发其他的子类化行为。这是一个虚函数,并有MFC自行调用。

view source
print?
1void CLabelLink::PreSubclassWindow()
2{
3  // 使控件能够接受鼠标消息
4  DWORDdwStyle = GetStyle();
5  ::SetWindowLong(GetSafeHwnd(), GWL_STYLE, dwStyle | SS_NOTIFY );
6 
7  CStatic::PreSubclassWindow();
8}

这个函数的作用很简单,只是稍微的增加了一个Style而已。但是这个Style对于整个类确实至关重要的。

如果你去翻MSDN,可能发现这个类的用处是让STATIC控件能够接受click消息,但实际上,在MSDN的深处,埋藏着这么一段不为人知的描述:

Applications often use static controls to label other controls or to separate a group of controls. Although static controls are child windows, they cannot be selected. Therefore, they cannot receive the keyboard focus and cannot have a keyboard interface. A static control that has the SS_NOTIFY style receives mouse input, notifying the parent window when the user clicks or double clicks the control. Static controls belong to the STATIC window class.

也就是说,这个Style能够让控件接受鼠标消息,所以才能有后面的动作。

其实,你也完全可以不要这段代码,只需要手动把控件的Notify属性改为True即可。但是考虑到类封装的原则,我还是保留了这一函数。

弄清楚了这些,剩下的问题就变得很简单了。

唔,剩下也没几个函数了……

view source
print?
01/*
02  输  入: bEnable(BOOL) - [in]TRUE:启用, FALSE:禁用
03  输  出: -
04  功  能: 启用或禁鼠标追踪
05*/
06void CLabelLink::EnableTrack(BOOL bEnable /* = TRUE */)
07{
08  m_bTrack = bEnable;
09  Invalidate(TRUE);
10}
11 
12/*
13  输  入: lpszTargetUrl(LPCTSTR) - [out]超链接地址
14  输  出: -
15  功  能: 设置目标超链接
16*/
17void CLabelLink::SetLinkUrl(LPCTSTR lpszTargetUrl)
18{
19  m_sTargetUrl = lpszTargetUrl;
20}
21 
22void CLabelLink::OnStnClicked()
23{
24  ShellExecute(NULL, _T("OPEN"), m_sTargetUrl, NULL, NULL, SW_SHOWNORMAL);
25}

至此,一个CLabelLink类,算是KO了。


3. 如何使用

俗话说得好,行百里者半九十。好不容易完成了CLabelLink,如果不知道怎么用,这类自然也就失去了意义。

其实呢,使用方法也很简单。

首先添加类文件到工程,往Dialog上拖拽一个STATIC控件,命名好ID。

接着往Dlg的head file中加入CLabelLink的head file,然后声明一个变量,比如:

CLabelLink m_ctlLink;

接下来我们需要利用DDE/DDX技术为变量和控件进行关联,在DoDataExchange函数中加入

DDX_Control(pDX, IDC_LBLLINKDEMO, m_ctlLink);

如果你对MFC的DDE/DDX技术不熟悉,你也可以通过Wizard的方式完成上述操作(右击控件,选择添加变量即可)。

上面只是准备操作,接下来在OnInitiDialog中加入

    m_ctlLink.SetTextColor(RGB(0, 0, 255));
    m_ctlLink.EnableTrack(TRUE);
    m_ctlLink.SetLinkUrl(_T(“http://www.google.com”));

于是,你就可以看到你的成果了

由于鼠标指针无法截图,所以只有Style照

CLabelLink的SRC和Demo可以在这里下载:Download CLabelLink_SRC&Demo

此文的PDF版可以在这里下载:Download PDF

 

Reference:http://blog.kingsamchen.com/archives/517

Others:带超链接的循环滚动静态控件http://www.evget.com/zh-CN/Info/ReadInfo.aspx?id=7225