下面,我們看一下它的制作過程:

一、新建一個以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() ) { //響應按鈕操作 } }
|
這里要求只有單擊按鈕的主體區時才進行響應。
至于單擊菜單項的響應與普通菜單一樣,這里不再詳述。
這個按鈕類還有幾點可考慮改進:
①增加無效狀態(變灰)按鈕;
②設置按鈕文字字體。