制作带弹出式菜单的按钮

来源:互联网 发布:软件租赁合同 编辑:程序博客网 时间:2024/05/01 19:39

带有弹出式菜单的按钮可以使一个按钮具有多项选择功能,扩展了按钮的功能,相当于把多个按钮集成于一体,可以减少按钮数目。

这种按钮的按钮体分为两个区域,单击主体区域时,执行主体按钮的功能,单击选择区域时,弹出一个菜单,可从中选择要执行的功能。如图所示。

下面,我们看一下它的制作过程:

一、新建一个以CButton类为基类的新类

单击“Insert”→“New Class”,建立一个新类。基类设置为CButton,新类起名为CMenuButton。

二、利用自绘方法绘制按钮,主体区显示按钮文本,选择区画一个小箭头

在CMenuButton类中用ClassWizard添加函数:PreSubclassWindow()和DrawItem()。

PreSubclassWindow()函数在建立按钮时执行,可用于做一些准备工作。在这里我给按钮添加自绘属性:

void CMenuButton::PreSubclassWindow()
{
    ModifyStyle( 0, BS_OWNERDRAW );    //设置按钮属性为自绘式
    
    CButton::PreSubclassWindow();
}

DrawItem()函数用于绘制按钮,左边绘制按钮文字,作为主体区,右边绘制一个小箭头,作为选择区。实际应用中,可根据具体需要绘制想要的形状和内容。

void CMenuButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    CDC *pDC = CDC::FromHandle( lpDrawItemStruct->hDC );
    m_ButRect = lpDrawItemStruct->rcItem;    //获取按钮尺寸
    int nSavedDC = pDC->SaveDC();
    VERIFY( pDC );

    DrawButton(pDC);    //绘制按钮

    pDC->RestoreDC( nSavedDC );
}

其中m_ButRect都是CRect型对象,在头文件中进行定义。DrawButton()为绘制按钮的函数,把它定义在外边的目的是方便用户修改,如果你想改变按钮形状,只需修改DrawButton()函数即可。

void CMenuButton::DrawButton(CDC *pDC)
{
    m_LRect.SetRect( m_ButRect.left, m_ButRect.top,
        m_ButRect.right-21, m_ButRect.bottom );  //按钮主体区尺寸
    m_RRect.SetRect( m_ButRect.right-20, m_ButRect.top,
        m_ButRect.right, m_ButRect.bottom );    //按钮选择区尺寸

    CPen Pen;
    Pen.CreatePen(PS_SOLID, 1, RGB(192,192,192) );
    pDC->SelectObject( &Pen );

    pDC->FillSolidRect( m_ButRect, m_BackColor );  //画背景
    switch( m_State )    //不同状态画不同边框
    {
    case 0:        //正常按钮
        pDC->DrawEdge( &m_LRect, BDR_RAISEDINNER, BF_RECT );
        pDC->DrawEdge( &m_RRect, BDR_RAISEDINNER, BF_RECT );
        break;
    case 1:        //鼠标进入时的按钮
        pDC->DrawEdge( &m_LRect, BDR_RAISEDINNER, BF_RECT );
        pDC->DrawEdge( &m_RRect, BDR_RAISEDINNER, BF_RECT );
        pDC->MoveTo( m_ButRect.TopLeft() );
        pDC->LineTo( m_ButRect.right, m_ButRect.top );
        break;
    case 2:        //单击按钮主体区时的按钮
        pDC->DrawEdge( &m_RRect, BDR_RAISEDINNER, BF_RECT );
        break;
    case 3:        //单击按钮选择区时的按钮
        pDC->DrawEdge( &m_LRect, BDR_RAISEDINNER, BF_RECT );
        break;
    }

    POINT    m_pt[3], m_ptCentre;        //箭头坐标(三个顶点)
    m_ptCentre = m_RRect.CenterPoint();  //选择区中点位置
    m_pt[0].x = m_ptCentre.x-3;        //计算箭头坐标
    m_pt[0].y = m_ptCentre.y-2;
    m_pt[1].x = m_ptCentre.x+4;
    m_pt[1].y = m_ptCentre.y-2;
    m_pt[2].x = m_ptCentre.x;
    m_pt[2].y = m_ptCentre.y+2;

    pDC->SelectStockObject( BLACK_BRUSH );  //定义画刷(黑色)
    CRgn rgn;
    rgn.CreatePolygonRgn( m_pt, 3, ALTERNATE );
    pDC->PaintRgn( &rgn );            //画选择区箭头

    pDC->SetTextColor( m_ForeColor ); //画主体区文字
    pDC->SetBkMode( TRANSPARENT );
    pDC->DrawText( m_strText, &m_LRect, DT_SINGLELINE | DT_CENTER
        | DT_VCENTER | DT_END_ELLIPSIS);
}

m_State是个标志,=0表示正常按钮;=1表示鼠标进入按钮,绘制暗线边框;=2表示在按钮主体区按下鼠标左键;=3表示在按钮选择区按下鼠标左键。

在m_State的不同取值下,绘制不同的按钮边框,可以增加按钮的动态效果。

三、添加鼠标响应函数

在CMenuButton类中用ClassWizard添加函数:OnMouseMove()、OnLButtonDown()、OnLButtonUp()。

OnMouseMove()函数用于响应鼠标移动消息,当鼠标进入按钮时,设置相应标志,并重绘按钮边框,当鼠标离开按钮时,清除标志,恢复原边框。

void CMenuButton::OnMouseMove(UINT nFlags, CPoint point)
{
    if( !b_InFlag || GetCapture()!=this )    //鼠标进入按钮
    {
        b_InFlag = true;    //设置进入标志
        SetCapture();       //捕获鼠标
        m_State = 1;        //置按钮状态(1-当前按钮)
        if( b_ClickFlag )   //检测单击选择区标志
        {
            m_Menu.Detach();    //清除打开的菜单
            m_Menu.DestroyMenu();
            b_ClickFlag = false;
        }
        Invalidate();       //重绘按钮
    }
    else
    {
        if ( !m_ButRect.PtInRect(point) )    //鼠标离开按钮
        {
            b_InFlag = false;    //清除进入标志
            ReleaseCapture();    //释放鼠标捕获
            b_ClickBut = false;  //清除单击标志
            m_State = 0;         //置按钮状态(0-正常按钮)
            if( b_ClickFlag )    //检测单击选择区标志
            {
                m_Menu.Detach();    //清除打开的菜单
                m_Menu.DestroyMenu();
                b_ClickFlag = false;
            }
            Invalidate();        //重绘按钮
        }
    }
    
    CButton::OnMouseMove(nFlags, point);
}

b_InFlag是个BOOL型量,鼠标进入时设置,离开时清除,目的是防止鼠标在按钮上移动时重复刷新按钮,以避免闪烁。

b_ClickFlag是单击按钮选择区标志,当它为true时,表示弹出菜单已打开,为false时表示菜单未弹出。当菜单已经弹出,而鼠标又移回按钮单击时,应清除菜单。

b_ClickBut是单击按钮主体区标志。

OnLButtonDown()函数响应按钮单击消息,当单击的是按钮主体区时,设置b_ClickBut标志;当单击的是按钮选择区时,要根据单击次数,决定是否弹出菜单。

void CMenuButton::OnLButtonDown(UINT nFlags, CPoint point)
{
    if( m_LRect.PtInRect(point) )    //单击按钮主体区
    {
        m_State = 2;        //置按钮状态(2-正常按钮)
        b_ClickBut = true;  //设置单击按钮标志
        Invalidate();       //重绘按钮
    }
    else if( m_RRect.PtInRect(point) && m_MenuID )  //单击选择区
    {
        m_State = 3;
        b_ClickBut = false;    //清除单击按钮标志
        Invalidate();          //重绘按钮
        b_ClickFlag = !b_ClickFlag;    //单击选择区标志
        if( b_ClickFlag )    //一次单击,弹出菜单
        {
            CRect rect = m_RRect;
            ClientToScreen(rect);    //转换为屏幕坐标
            point = rect.BottomRight();
            point.x -= rect.Width(); //设置弹出菜单的位置

            VERIFY(m_Menu.LoadMenu(m_MenuID));  //装入菜单资源

            CMenu* pPopup = m_Menu.GetSubMenu(0);
            ASSERT(pPopup != NULL);
            CWnd* pWndPopupOwner = this;

            while (pWndPopupOwner->GetStyle() & WS_CHILD)
                pWndPopupOwner = pWndPopupOwner->GetParent();

            pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON,
                point.x, point.y, pWndPopupOwner);  //弹出菜单
        }
        else        //再次单击,清除菜单
        {
            m_Menu.Detach();
            m_Menu.DestroyMenu();
        }
    }
    
    CButton::OnLButtonDown(nFlags, point);
}

当单击按钮选择区时,在选择区的左下角弹出菜单,函数中的point是鼠标的屏幕坐标,求菜单位置时也使用屏幕坐标。

m_MenuID是与按钮关联的弹出菜单的ID,它在创建按钮时进行设置。

OnLButtonUp()函数响应按钮弹起消息,这是只要恢复按钮正常状态即可,以产生单击动画效果。

void CMenuButton::OnLButtonUp(UINT nFlags, CPoint point)
{
    m_State = 0;    //恢复为正常按钮
    Invalidate();   //重绘按钮
    
    CButton::OnLButtonUp(nFlags, point);
}

四、自定义接口函数

提供用户使用按钮的接口。

//设置关联菜单ID
void CMenuButton::SetMenuID(int nID)
{
    m_MenuID = nID;
}

//设置按钮文本
void CMenuButton::SetText(CString str)
{
    m_strText = str;
}

//设置文本颜色
void CMenuButton::SetForeColor(COLORREF color)
{
    m_ForeColor = color;
    Invalidate();
}

//设置背景颜色
void CMenuButton::SetBkColor(COLORREF color)
{
    m_BackColor = color;
    Invalidate();
}

//是否单击主按钮区
BOOL CMenuButton::isClick()
{
    return b_ClickBut;
}

由于这种按钮分为两个区域,使用时要根据单击区域决定要做的工作,所以设置了isClick()接口函数。

五、变量的初始化

在CMenuButton类的头文件中,定义有以下变量和函数:

MenuButton.h

private:
    int         m_State;        //按钮状态
    BOOL        b_InFlag;       //鼠标进入标志
    BOOL        b_ClickFlag;    //单击选择区
    BOOL        b_ClickBut;     //单击主体区
    CString     m_strText;      //按钮文字
    COLORREF    m_ForeColor;    //文本颜色
    COLORREF    m_BackColor;    //背景色
    CRect       m_ButRect;      //按钮尺寸
    CRect       m_LRect;        //按钮左部尺寸
    CRect       m_RRect;        //按钮右部尺寸
    CMenu       m_Menu;         //弹出菜单
    int         m_MenuID;       //菜单ID

    void DrawButton(CDC *pDC);    //绘制按钮

public:
    CMenuButton();        //构造函数
    void SetMenuID(int nID);        //设置关联菜单ID
    void SetForeColor(COLORREF color);   //设置文本颜色
    void SetBkColor(COLORREF color);     //设置背景颜色
    void SetText(CString str); //设置按钮文本
    BOOL isClick();            //是否单击主按钮区

各变量的初始化在构造函数中进行:

CMenuButton::CMenuButton()
{
    m_MenuID = 0;        //菜单ID
    b_InFlag = false;    //进入标志
    m_State = 0;         //初始状态
    b_ClickFlag = false; //单击选择区标志
    b_ClickBut = false;  //单击主体区标志
    m_strText = _T("");  //按钮文本
    m_ForeColor = RGB(0,0,0);    //文字颜色
    m_BackColor = GetSysColor( COLOR_3DFACE );  //背景色
}

这样,带菜单的按钮类就做好了,用它定义的按钮实例可以连接一个弹出式按钮,下面我们就看看怎样定义按钮实例。

六、生成按钮实例

1、在对话框中放置按钮,把它的大小调整合适;

2、用ClassWizard为按钮添加变量,把变量的类型设置为CMenuButton;

3、定义按钮的关联菜单

关联菜单就是普通的弹出式菜单,用VC的菜单编辑器生成即可。

进入资源的“Menu”项,插入一个新的菜单,根据需要添加菜单项。

4、设置按钮

在对话框的OnInitial()函数中初始化按钮:

按钮变量.SetMenuID( 菜单ID );
按钮变量.SetText( 按钮文本 );
按钮变量.SetBkColor( 按钮背景色 );

七、按钮的响应

对按钮的响应包括单击按钮主体区的响应和单击菜单项的响应两部分。

用ClassWizard添加按钮的响应函数和各菜单项的响应函数。

在按钮的响应函数中要做如下工作:

void CMBTestView::OnMenubutton1()
{
    if( m_MenuButton1.isClick() )
    {
        //响应按钮操作
    }
}

这里要求只有单击按钮的主体区时才进行响应。

至于单击菜单项的响应与普通菜单一样,这里不再详述。

这个按钮类还有几点可考虑改进:

①增加无效状态(变灰)按钮;

②设置按钮文字字体。

相应方法可参考文章《可设置字体和颜色的按钮》。

示例程序界面:

示例程序界面

文本所用MFC函数速查:

CDC::DrawEdge
CDC::DrawText
CDC::FillSolidRect
CDC::PaintRgn
CDC::RestoreDC
CDC::SaveDC
CDC::SelectObject
CDC::SelectStockObject
CDC::SetBkMode
CDC::SetTextColor
CMenu::DestoryMenu
CMenu::Detach
CMenu::LoadMenu
CMenu::TrackPopupMenu
CPen::CreatePen
CRect::BottomRight
CRect::CenterPoint
CRect::PtInRect
CRect::SetRect
CRect::Width
CRgn::CreatePolygonRgn
CWnd::ClientToScreen
CWnd::GetCapture
CWnd::GetParent
CWnd::GetStyle
CWnd::Invalidate
CWnd::ModifyStyle
CWnd::ReleaseCapture
CWnd::SetCapture

下载示例程序
http://221.199.150.103/jsj/Html/vc/wen/vcwen09.htm

原创粉丝点击