(转)用MFC创建菜单按钮

来源:互联网 发布:网络的实用性 编辑:程序博客网 时间:2024/05/15 09:53
 

用MFC创建菜单按钮

现在有不少的软件都有这样的一种界面效果:当用户单击某一个按钮之后,并不是简单地执行某种功能或弹出一个对话框,而是在按钮旁边弹出一个菜单,让用户作更详细地选择,这在某种程度上就代替了简单的对话框,而且较对话框更为“用户友好”。这样的按钮基本上有两种类型:在按钮上显示文字的和在按钮上显示箭头的,显示箭头常见的有向右的和向下的两种,还有向上的和向左的。图示为常见的风格,即向下的箭头和在按钮左下角弹出菜单。那么,我们在编程时如何实现这一功能呢?

 

我们知道,MFC中的CButton类有一个虚函数名叫DrawItem(),若在对话框模板中为控件指定了BS_OWNERDRAW风格,则在运行时将调用这个函数来画按钮,而CMenu类的成员函数TrackPopupMenu()则可以在屏幕的任何位置弹出菜单。由上得到启发,只要我们合理地使用这两个函数,就能创建出“菜单按钮”来。

下面的CMenuButton类封装了全部的这些功能,让我们先来看一下它的制作原理。


在取得了按钮的矩形区域之后,取其一个角落的值传递给TrackPopupMenu()函数即可实现弹出菜单,在TrackPopupMenu内部使用TPM_RETURNCMD标志可以得到用户选择的菜单的命令ID,以供进一步的处理;在重载了DrawItem()函数之后,我们可以在函数的内部使用CDC::DrawFrameControl()函数来画出基本的按钮外观,再在中间部位画一个箭头即可。箭头可以用Marlett字体来画。也许有人会担心,若果其他人的机器没装Marlett字体怎么办?其实,任何一台安装Windows的机器离开了Marlett字体都无法正常工作,先请看下图,这是Windows“系统工具”中自带的“字符映射表”。

看到最上面一行中的那几个箭头了吗?就是要把它们画在按钮上。等一等,另外的几个符号怎么也那么熟悉?这不就是几乎每个窗口上都有的“最小化”、“还原”、“关闭”和“最大化”按钮吗?不错,Windows正是使用这几个字符在标题栏上绘图的。其实,Windows中的最“标准”的画箭头的方法就是使用Marlett字体,无论是工具栏上的箭头还是组合框中的箭头,都是这样画出的。有时,在乱删了字体之后,组合框或工具栏的下拉箭头会变成数字6或者9,为什么?看到状态栏上的“击键值”了吗?——“6”,往右数,那个小一点的下箭头正好是——“9”。

下面是具体的制作过程。

首先,生成一个MFC AppWizard EXE 工程,最好是基于对话框的工程,当然,利用现有的工程也可以。

生成一个以CButton为基类的新类,名为CMenuButton,然后用ClassWizard为其添加两个成员函数:DrawItem()和PreSubclassWidnow();手工为CMenuButton类添加BOOL类型m_bDrawFocusRect成员变量,用于决定是否在按钮上画焦点矩形,添加SetDrawFocusRect()函数用于设置这个标志,默认为画焦点矩形;添加两个枚举类型的变量m_ArrowType和m_PopupPos,用于决定所画的箭头的类型和菜单弹出的位置。箭头可为右箭头、下箭头、小右箭头、小下箭头、上箭头和左箭头(参见本文开始处的图);菜单的弹出位置可以为按钮的左上角、右上角、左下角和右下角。最后手工添加两个函数,SetArrowType()和SetMenuPopupPos(),用于设置以上各种风格,其默值分别为画右箭头和在左下角弹出。

如果只需要菜单而不需要画箭头,只需置空BS_OWNERDRAW标志位即可,添加一个SetStyle()函数,用于设置是画箭头还是显示文本,其默认值是画箭头。

为方便处理按钮的BN_CLICKED通知消息,为CMenuButton类创建一个公有的成员函数OnClick(),以便在BN_CLICKED的消息处理器中调用。它有两个参数,第一个是菜单资源的ID,第二个参数为子菜单的ID,默认为0。如果只有一组子菜单,则可使用其默认值0。OnClick()函数的返回值为所选的菜单项的命令ID,若未作任何有效选择,则返回0。

下面是程序代码。

头文件:

 

#if !defined(_EWAY_MEMUBUTTON_H__INCLUDED_)

#define _EWAY_MEMUBUTTON_H__INCLUDED_

 

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

 

// MenuButton.h : header file

//

 

class CMenuButton : public CButton

{

public:

       CMenuButton();

       virtual ~CMenuButton();

       DECLARE_DYNAMIC( CMenuButton )

 

       enum ArrowType

       {

              arrowRight,//向右的箭头;

              arrowDown, //向下的箭头;

              arrowSmallRight, //向右的小箭头;

              arrowSmallDown, //向下的小箭头;

              arrowUp, //向上的箭头;

              arrowLeft//向左的箭头;

       }m_ArrowType;

       enum PopupPos

       {

              //名称为TopLeft等等,遵守英文习惯;

              posTopLeft, //左上角;

              posBottomLeft, //左下角;

              posTopRight, //右上角;

              posBottomRight, //右下角;

       }m_PopupPos;

       virtual UINT OnClick(UINT nIDMenuResource,UINT nSubMenu=0);

       void SetArrowType(CMenuButton::ArrowType type=CMenuButton::arrowRight);

       void SetDrawFocusRect(BOOL bDrawFocusRect=TRUE);

       void SetMenuPopupPos(CMenuButton::PopupPos pos=CMenuButton::posBottomLeft);

       void SetStyle(BOOL bDrawArrow=TRUE);

 

       // Overrides

       // ClassWizard generated virtual function overrides

       //{{AFX_VIRTUAL(CMenuButton)

       public:

       virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);

       protected:

       virtual void PreSubclassWindow();

       //}}AFX_VIRTUAL

 

protected:

       BOOL m_bDrawFocusRect;

       //{{AFX_MSG(CMenuButton)

       //}}AFX_MSG

       DECLARE_MESSAGE_MAP()

};

 

//{{AFX_INSERT_LOCATION}}

// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

 

#endif // !defined(_EWAY_MEMUBUTTON_H__INCLUDED_)

 

实现文件:

 

// MenuButton.cpp : implementation file

//

 

#include "stdafx.h"

#include "MenuButtonTest.h"

#include "MenuButton.h"

 

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

 

/////////////////////////////////////////////////////////////////////////////

// CMenuButton

 

IMPLEMENT_DYNAMIC( CMenuButton, CButton )

 

CMenuButton::CMenuButton()

{

       SetArrowType();

       SetDrawFocusRect();

       SetMenuPopupPos();

}

 

CMenuButton::~CMenuButton()

{

}

 

 

BEGIN_MESSAGE_MAP(CMenuButton, CButton)

       //{{AFX_MSG_MAP(CMenuButton)

       //}}AFX_MSG_MAP

END_MESSAGE_MAP()

 

/////////////////////////////////////////////////////////////////////////////

// CMenuButton message handlers

 

void CMenuButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)

{

       //使用FromeHandle()创建临时的对象,若使用Attach()则必需在最后使用Detach()

       CDC *pDC= CDC::FromHandle(lpDrawItemStruct->hDC);

 

       //得到画笔的颜色;

       CPen pen;

       if( (lpDrawItemStruct->itemState&ODS_DISABLED) )

       {

              pen.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_GRAYTEXT) );

       }

       else

       {

              pen.CreatePen(PS_SOLID, 0, ::GetSysColor(COLOR_BTNTEXT) );

       }

       CPen * pOldPen = pDC->SelectObject(&pen);

 

       CFont font;

       font.CreateFont(12, 0, 0, 0, FW_NORMAL, 0, 0, 0,

                              DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,

                              CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,

                              DEFAULT_PITCH|FF_SWISS, "Marlett");

 

       CFont * pOldFont=pDC->SelectObject(&font);

       CSize size=pDC->GetTextExtent("4",1);

 

       //决定箭头的样子,具体的值可在“字符映射表”中查得;

       CString strArrow;

       switch (m_ArrowType)

       {

       case CMenuButton::arrowRight:

              strArrow="4";

              break;

       case CMenuButton::arrowDown:

              strArrow="6";

              break;

       case CMenuButton::arrowSmallRight:

              strArrow="8";

              break;

       case CMenuButton::arrowSmallDown:

              strArrow="9";

              break;

       case CMenuButton::arrowUp:

              strArrow="5";

              break;

       case CMenuButton::arrowLeft:

              strArrow="3";

              break;

       default:

              ASSERT(FALSE);

       }

 

       //计算座标值,用于绘制箭头;

       int x=(lpDrawItemStruct->rcItem.right-lpDrawItemStruct->rcItem.left-size.cx)/2;

       int y=(lpDrawItemStruct->rcItem.bottom-lpDrawItemStruct->rcItem.top-size.cy)/2;

      

       //画按钮与箭头;

       if( (lpDrawItemStruct->itemState&ODS_SELECTED) )

       {

              pDC->DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, DFCS_BUTTONPUSH|DFCS_PUSHED);

              //在按钮被按下时,上面的字符要有一个向右和向下的偏移;

              pDC->TextOut(++x,++y,strArrow);

       }

       else

       {

              pDC->DrawFrameControl(&lpDrawItemStruct->rcItem, DFC_BUTTON, DFCS_BUTTONPUSH);

              pDC->TextOut(x,y,strArrow);

       }

 

       //如果需要,画焦点矩形;

       if( (lpDrawItemStruct->itemState&ODS_FOCUS) && m_bDrawFocusRect)

       {

              CRect rectFocus(lpDrawItemStruct->rcItem);

              rectFocus.DeflateRect(3,3);   //看起来比较接近的值;

              pDC->DrawFocusRect(rectFocus);

       }

 

       //仅将对象选回即可,不必调用DeleteTempMap();

       pDC->SelectObject(pOldPen);

       pDC->SelectObject(pOldFont);

}

 

UINT CMenuButton::OnClick(UINT nIDMenuResource, UINT nSubMenu/*=0*/)

{

       CMenu menu;

       //装载菜单;

       VERIFY(menu.LoadMenu(nIDMenuResource) );

 

       //得到子菜单;

       CMenu *pPopup = menu.GetSubMenu(nSubMenu);//默认为第一组子菜单;

       ASSERT(pPopup != NULL);

 

       CRect rect;

       GetWindowRect(rect);

 

       POINT point;

       //决定弹出菜单的位置;

       switch (m_PopupPos)

       {

       case CMenuButton::posTopLeft://左上角;

                     point.x=rect.left;

                     point.y=rect.top;

                     break;

       case CMenuButton::posBottomLeft://左下角;

                     point.x=rect.left;

                     point.y=rect.bottom;

                     break;

       case CMenuButton::posTopRight://右上角;

                     point.x=rect.right;

                     point.y=rect.top;

                     break;

       case CMenuButton::posBottomRight://右下角;

                     point.x=rect.right;

                     point.y=rect.bottom;

                     break;

       default:

              ASSERT(FALSE);

       }

 

       //弹出菜单;

       UINT nMenuSel = pPopup->TrackPopupMenu((TPM_LEFTALIGN|TPM_LEFTBUTTON |TPM_NONOTIFY |TPM_RETURNCMD),point.x, point.y, this);

       pPopup->DestroyMenu();

 

       //返回被选择的菜单的ID,如果无任何有效的选择,将返回0;

       return nMenuSel;

}

 

void CMenuButton::PreSubclassWindow()

{

       CButton::PreSubclassWindow();

       //默认值:加入BS_OWNERDRAW风格;

       ModifyStyle(0,BS_OWNERDRAW);

}

 

void CMenuButton::SetArrowType(CMenuButton::ArrowType type)

{

       m_ArrowType=type;

}

 

void CMenuButton::SetDrawFocusRect(BOOL bDrawFocusRect)

{

       m_bDrawFocusRect=bDrawFocusRect;

}

 

void CMenuButton::SetMenuPopupPos(CMenuButton::PopupPos pos)

{

       m_PopupPos=pos;

}

 

void CMenuButton::SetStyle(BOOL bDrawArrow)

{

       if(bDrawArrow)

       {

              ModifyStyle(0,BS_OWNERDRAW,SWP_NOMOVE|SWP_NOZORDER| SWP_NOSIZE);

       }

       else

       {

              ModifyStyle(BS_OWNERDRAW,0,SWP_NOMOVE|SWP_NOZORDER| SWP_NOSIZE);

       }

}

要使用这个类,为对话框添加CMenuButton类型的按钮成员变量,若需改变默认风格,则可在OnInitDialog中调用CMenuButton类的公有成员函数SetArrowType()、SetDrawFocusRect()、SetMenuPopupPos()或SetStyle(),在ClassWizard中为对话框添加按钮的BN_CLICKED消息处理函数,然后在其中调用CMenuButton类的OnClick()成员函数,并指定一个菜单ID给它,最后处理OnClick()函数的返回值即可。下面是一个例子。

 

void CMenuButtonTestDlg::OnTest()

{

       UINT nSel=m_btnTest.OnClick(IDR_POPUP);

       switch(nSel)

       {

       case ID_APP_EXIT:

              SendMessage(WM_CLOSE,0,0);

              break;

       case ID_POPUP_ITEM1:

              AfxMessageBox("您选择了第一项!");

              break;

       case ID_POPUP_ITEM2:

              AfxMessageBox("您选择了第二项!");

              break;

       case ID_POPUP_ITEM3:

              AfxMessageBox("您选择了第三项!");

              break;

       default:

              //Do nothing;

              ;

       }

}

所用菜单的资源描述如下,外观可参见文首的图。

IDR_POPUP MENU DISCARDABLE

BEGIN

    POPUP "_POPUP_"

    BEGIN

        MENUITEM "第一项",                      ID_POPUP_ITEM1

        MENUITEM "第二项",                      ID_POPUP_ITEM2

        MENUITEM "第三项",                      ID_POPUP_ITEM3

        MENUITEM SEPARATOR

        MENUITEM "退出(&X)",                    ID_APP_EXIT

    END

END

 

BOOL CMenuButtonTestDlg::OnInitDialog()

{

       CDialog::OnInitDialog();

       //因为这几个值都是默认值,所以注释掉,仅为了演示用法;

       //m_btnTest.SetDrawFocusRect(TRUE);

       //m_btnTest.ArrowType (CMenuButton::arrowRight);

       //m_btnTest.SetStyle(TRUE);

       //m_btnTest.SetMenuPopupPos(CMenuButton::posBottomLeft);

       return TRUE;

}

值得补充说明的是,使用CMenuButton类的时候,并不一定需要在对话框模板中为按钮指定BS_OWNERDRAW风格,因为在缺省情况下,CMenuButton类的PreSubclassWindow()函数中已经自动加入了这一风格。

原创粉丝点击