MFC 9中的新控件Command Link Button及在Vista之前平台上的应用

MFC 9中的新控件Command Link Button及在Vista之前平台上的应用

 

 

         什么是Command Link

 

         Command LinkVista中是样新事物,请看下图:

 

 

 

 

         它实质上有两部分:主文本(Main Text)及注释文本(Note Text),如下图:

 

 

 

 

         一个Command Link,实际上不是一个新的控件类,它只是Button类的一个新样式,只需对Button控件添加BS_COMMANDLINK样式便可看上去像Command Link

 

 

         MFCCommand Link的增强之处

 

         Visual Studio 2008中,MFC得到了一定程度的增强以适应Vista的新特性,现在MFC已做到了与Vista的同步,因此,下列成员函数被添加到CButton类中以实现Command Link

 

 

Ø  CButton::GetNote

Ø  CButton:GetNoteLength

Ø  CButton:SetNote

 

 

         MFC函数的命名规则中,就大致可以猜出这些函数的功能来,另外,资源管理器的工具箱也添加了这些新控件,见下图:

 

 

 

 

         问题来了

 

         程序员经常会写一些面向多平台的程序,在典型的开发过程中,通常是一个二进制程序要面向操作系统的多个版本,这就要求,作为一个程序开发者,要么减少在所有操作系统版本上不通用的特性,要么用一些变通方法,在后台写一些特定于某种操作系统平台的代码,让程序功能尽可能地保持一致。

         在用户界面这一块,尤其是程序要求有跨所有平台、设计良好的人机界面时,这个问题尤为严重,因为这一点涉及到用户如何与软件进行交互。在外观感觉、软件行为表现方面任何的不同,都会降低用户采用该软件的速度,且客户支持中心的来电也会直线上升,如此等等。

         多数时候,大部分的软件都会与纸质用户手册一并发售,所以软件统一的外观感觉是必须的,否则,多个软件版本的维护可不是件轻松事。

         那这与Command Link有什么关系呢?Command LinkVista之前的平台上并不存在,所以,如果一个软件用Command Link实现了更友好的用户界面,那么在Vista之前的平台上,怎能仅是功能上的模仿呢?

         当用Visual Studio 2008MFC 9)创建一个MFC对话框项目时,这个问题就会更加明显,在对话框上放置一个Command Link,生成之后运行它。如果开发环境早于Vista,就会见不到Command Link,但在Vista上运行同一程序,它又出现了。

 

 

         变通方法

 

         这里讲的“变通方法”并不是一个彻底的解决方案,彻底的解决方案可在Vista之前版本上实现同一行为表现,但这个方法像是重写控件本身。前面的问题说到,在Vista之前的平台上,通用控件DLL不能解释BS_COMMANDLINK,且不管从哪方面看,微软也不像要在Vista之前的平台上提供新版的commctrl.dll,不过,Command Link非常简单,也许有种方法可在Vista之前的平台至少提供相似的功能。

         既然BS_COMMANDLINK帮不上忙,那要是在Vista平台上使用它,而在之前的平台上不用它呢,又怎样?当然行得能了,因为它就是一个普通的按钮控件,有以下两种方法:

 

 

Ø  通过button.ModifyStyle(BS_COMMANDLINK,0);让程序自己去掉它。

Ø  MFC方式子类化按钮控件,并修改它。要是编写自己的类,比如说CCGCommandLinkButton,从CButton派生并在类中进行处理呢?用法就非常简单了,使用者只需用CCGCommandLinkButton对象,在模板中关联Command Link资源就行了。

 

 

         下面就开始了,在VS2008中建一个基于对话框的MFC项目,所有均为默认值,在对话框上放一个“Command Button Control”,生成并运行。出于本文讨论的目的,要在Vista之前的平台上进行测试(Vista自身当然能运行了),这时是一个带有OKCancel按钮的对话框,但没有Command Link按钮。

 

 

         1、关闭VS2008中的对话框资源。找到菜单“项目”——“添加类”,选择右边的“MFC”,选择“MFC类”,单击“添加”,类名写为“CCGCommandLinkButton”。把基类从CWnd改为CButton,单击“完成”。

         2、转到资源视图,打开对话框资源,选择Command Link Button,鼠标右键单击选择“添加变量”。把变量名改为,如“m_btnCommand”,单击“完成”。

         3、打开对话框类头文件,在最上方,添加:#include "CGCommandLinkButton.h",并把m_btnCommand的声明类型从CButton改为CCGCommandLinkButton

         4、生成并运行。还是看不到Command Link Button

         5、下一步,要在非Vista平台上去掉BS_COMMANDLINK样式属性,为简单起见,在CCGCommandLinkButton类中添加一个private BOOL变量m_bPreVista,并在构造函数中,把它设为TRUE;以后,就可通过查询Windows的版本来设置这个值。

 

 

CCGCommandLinkButton::CCGCommandLinkButton()

{

   m_bPreVista = TRUE;

}

 

 

         6、打开文件CCGCommandLinkButton.h,如果此时在头文件的类范围(class scope)中,应该可看到带有“覆盖”按钮的属性面板,从列表中选择PreCreateWindow,从下拉框中添加PreCreateWindow,同样地,添加PreSubclassWindow

         7PreCreateWindow是一个虚函数,其作为CWnd::Create/Ex函数的一部分,在窗口实际创建之前被调用,我们可在此修改CREATESTRUCT,以便其后使用Create/Ex函数创建按钮时,去掉BS_COMMANDLINK样式属性。

 

 

BOOL CCGCommandLinkButton::PreCreateWindow(CREATESTRUCT& cs)

{

   // TODO: Add your specialized code here and/or call the base class

 

   if(TRUE == m_bPreVista)

   {

      //去掉BS_COMMANDLINK样式属性,因为它不能应用于Vista之前的平台

      cs.style &= (~BS_COMMANDLINK);

   }

   return CButton::PreCreateWindow(cs);

}

 

 

         8PreSubclassWindow也是一个虚函数,作为MFC DoDataExchange的一部分,在第一次子类化控件时被调用,我们可在窗口创建之后修改任意属性。这在对话框模板中非常有用,尤其是太晚而不能影响创建过程情况时。在此也要去掉BS_COMMANDLINK样式属性。

 

 

void CCGCommandLinkButton::PreSubclassWindow()

{

   // TODO: Add your specialized code here and/or call the base class

   if(TRUE == m_bPreVista)

   {

      //去掉BS_COMMANDLINK样式属性,因为它不能应用于Vista之前的平台

      ModifyStyle(BS_COMMANDLINK,0);

   }

   CButton::PreSubclassWindow();

}

 

 

         9、以上的代码非常简单,它们在Vista之前的平台上去掉了BS_COMMANDLINK样式属性,生成并运行,现在应该就可以看到按钮了。

         10、现在,打开对话框类的实现文件,并在OnInitDialog后添加代码设置注释信息:

 

 

m_btnCommand.SetNote(_T("This is a test note"));

return TRUE;    // return TRUE unless you set the focus to a control

 

 

         生成并运行,咦,什么也没变啊,注释文本并没有出现。

         11、现在的任务就是要使按钮看起来至少像个Command Link,这就需要显示按钮文本,并在其下显示注释,有以下两种方法:

Ø  可把注释与实际的窗体文本(Windows Text)组合在一起,在两者之间有插一个新行,之后再把组合后的文本设为窗体文本。然而,在对话框上实验之后并不可行。

Ø  另一种方法是把两段文本分开对待,并把它们“画”到按钮上。这意味着需要一个自绘制按钮(BS_OWNERDRAW),这是可行的,因为使用Command Link按钮的大多数人八成不会用到BS_OWNERDRAW属性,如果要使用BS_OWNERDRAW来自绘制,那干嘛还使用Command Link样式呢?所以可行。

         12、修改PreCreateWindowPreSubclassWindow以添加BS_OWNERDRAW样式属性。

 

 

BOOL CCGCommandLinkButton::PreCreateWindow(CREATESTRUCT& cs)

{

   // TODO: Add your specialized code here and/or call the base class

   if(TRUE == m_bPreVista)

   {

      //去掉BS_COMMANDLINK样式属性,因为它不能应用于Vista之前的平台

      cs.style &= (~BS_COMMANDLINK);

      //添加OWNERDRAW,因为现在要我们自己绘制文本及注释

      cs.style |= BS_OWNERDRAW;

   }

   return CButton::PreCreateWindow(cs);

}

 

void CCGCommandLinkButton::PreSubclassWindow()

{

   // TODO: Add your specialized code here and/or call the base class

   if(TRUE == m_bPreVista)

   {

      //去掉BS_COMMANDLINK样式属性,因为它不能应用于Vista之前的平台

      //添加OWNERDRAW,因为现在要我们自己绘制文本及注释

      ModifyStyle(BS_COMMANDLINK,BS_OWNERDRAW);

   }

   CButton::PreSubclassWindow();

}

 

 

         13、添加BS_OWNERDRAW属性后意味着你要自己绘制按钮,一般来说,这通常是由父窗口完成的,但是在MFC中,通过重载DrawItem,可以把它做成一个自包含的功能类,就像添加PreCreateWindow一样,在CCGCommandLinkButton类中重载DrawItem,下面的代码只是简单地绘制窗口文本:

 

 

void CCGCommandLinkButton::DrawItem(LPDRAWITEMSTRUCT

                                    lpDrawItemStruct)

{

   // TODO:  Add your code to draw the specified item

   CString szWindowText;

   GetWindowText(szWindowText);

 

   CDC dc;

   dc.Attach(lpDrawItemStruct->hDC);

   dc.SetBkMode(TRANSPARENT);

   dc.DrawText(szWindowText,&lpDrawItemStruct->rcItem,DT_LEFT);

   dc.Detach();

}

 

 

         14、现在,怎样获取注释文本呢?如果你认为是GetNote()的话,将会发现它不起作用,因为在Vista之前的平台上根本就没有,那么要怎样获取文本呢?

         15、要知道调用SetNote(),只不过是对SendMessage BCM_SETNOTE的调用,且把文本作为LParam传递。这给了我们一些提示,可以在CCGCommandLinkButton类中处理这个消息,并把它存在一个CString成员变量中以备用,记住只能在Vista之前的平台上这样做。

         打开CCGCommandLinkButton.h文件,添加一protected函数:

 

 

afx_msg LRESULT OnSetNote(WPARAM wParam, LPARAM lParam);

 

 

         添加一个名为m_szNoteprivate CString变量,打开CCGCommandLinkButton.cpp,添加一个消息映射:

 

 

BEGIN_MESSAGE_MAP(CCGCommandLinkButton, CButton)

   ON_MESSAGE(BCM_SETNOTE,&OnSetNote)

END_MESSAGE_MAP()

 

 

         添加实现部分:

 

 

LRESULT CCGCommandLinkButton::OnSetNote(WPARAM wParam,

                                        LPARAM lParam)

{

   if(TRUE == m_bPreVista)

   {

      m_szNote = (LPTSTR)lParam;

      //当注释更改时,强制Invalidate()

      UpdateWindow();

   }

   else

   {

      //如果是Vista及以上版本,默认就行了。

      Default();

   }

   return TRUE;

}

 

 

         注释部分需要在DrawItem的实现中更新,最简单的是在窗口文本下绘制注释文本,但在此我们多做一点,让窗口文本为黑体,而注释文本不变,以下是代码:

 

 

void CCGCommandLinkButton::DrawItem(LPDRAWITEMSTRUCT

                                    lpDrawItemStruct)

{

   // TODO:  Add your code to draw the specified item

   RECT itemRect = lpDrawItemStruct->rcItem;

 

   CString szWindowText;

   GetWindowText(szWindowText);

 

   CDC dc;

   dc.Attach(lpDrawItemStruct->hDC);

 

   //绘制背景

   CBrush brBtnShadow;

   brBtnShadow.CreateSolidBrush(GetSysColor(COLOR_BTNSHADOW));

   dc.FrameRect(&itemRect, &brBtnShadow);

 

   //取得当前字体

   CFont* pFont = GetFont();

   CFont* pOldFont = NULL;

   CFont boldFont;

   if(pFont)

   {

      LOGFONT lf;

      pFont->GetLogFont(&lf);

      lf.lfWeight = FW_BOLD;

      boldFont.CreateFontIndirect(&lf);

      pOldFont = dc.SelectObject(&boldFont);

   }

 

   InflateRect(&itemRect, -5, -5);

   RECT oldRect = itemRect;

 

   //首先,取得需绘制窗口文本的矩形区

   //可从返回的矩形区知道用于绘制窗口文本的高度是多少

   //因此,绘制注释文本的坐标就大致可得出了

   dc.DrawText(szWindowText,&itemRect,DT_LEFT | DT_WORDBREAK |

               DT_CALCRECT );

   //现在开始绘制了

   dc.DrawText(szWindowText,&itemRect,DT_LEFT | DT_WORDBREAK);

   if(pFont)

   {

      //设成黑体之后,恢复原字体

      dc.SelectObject(pOldFont);

   }

   //绘制注释文本

   oldRect.top = itemRect.bottom + 5;

   dc.DrawText(m_szNote,&oldRect,DT_LEFT | DT_WORDBREAK);

 

   dc.Detach();

}

 

 

         生成并运行,就可看到按钮及下文的注释都以正确的字体显示了。

 

 

         16、再来看一下SetNote,现在,又多出两个方法了:GetNoteGetNotelength,为BCM_GETNOTEBCM_GETNOTELENGTH分别添加消息映射,以下是它们的实现部分:

 

 

ON_MESSAGE(BCM_GETNOTELENGTH,&OnGetNoteLength)

ON_MESSAGE(BCM_GETNOTE,&OnGetNote)

 

LRESULT CCGCommandLinkButton::OnGetNoteLength(WPARAM wParam,

                                              LPARAM lParam)

{

   if(TRUE == m_bPreVista)

   {

      return m_szNote.GetLength();

   }

   else

   {

      //如果是Vista及以上版本,默认就行了。

      return Default();

   }

}

 

LRESULT CCGCommandLinkButton::OnGetNote(WPARAM wParam,

                                        LPARAM lParam)

{

   if(TRUE == m_bPreVista)

   {

      //首先,检查长度是否足够

      DWORD dwRequiredLen = m_szNote.GetLength() + 1;

      DWORD* pSize = (DWORD*)wParam;

      if((*pSize) < dwRequiredLen)

      {

         //如果空间不够,把 *wParam设为所需长度

         //并把最后的错误设为ERROR_INSUFFICIENT_BUFFER

         *pSize = dwRequiredLen;

         SetLastError(ERROR_INSUFFICIENT_BUFFER);

         return FALSE;

      }

      else

      {

         //复制文本到lParam

         _tcscpy_s((LPTSTR)lParam,(*pSize),m_szNote);

         return TRUE;

      }

   }

   else

   {

      return Default();

   }

}

 

 

         最终结果如下图:

 

 

 

 

         17、剩下的就是在运行时设置m_bPreVista变量了,可用GetVersionEx API来检查主版本,Vista的主版本号为6。下面是CCGCommandLinkButton构造函数中的代码:

 

 

CCGCommandLinkButton::CCGCommandLinkButton()

{

   OSVERSIONINFO osvi;

 

   ZeroMemory(&osvi, sizeof(OSVERSIONINFO));

   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

 

   GetVersionEx(&osvi);

 

   m_bPreVista = osvi.dwMajorVersion < 6;

}

 

 

         生成并运行,现在exe就可运行在Vista及之前平台上了。在Vista之前平台上,程序如下图:

 

 

 

   

         Vista上,程序如下图:

 

 

 

 

         结论

         以上绝不是一个彻底的解决方案,它没有对不同状态下的按钮外观进行处理,也没有涉及到系统主题,本文的目的在于为多种操作系统编写通用代码,而无须损失某些新特性,Command Link按钮相对复选框及单选按钮来说非常直观,可以在以后的程序中加以采用。

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值