天天看點

MFC自定義按鈕的實作

    學習MFC的都知道,我們要想改變對話框和控件的背景以及文本顔色,可以響應OnCtlColor消息,在這個函數裡面進行相應的設定,如下所示:

HBRUSH StatusBar::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
 HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);

 // TODO:  Change any attributes of the DC here 

 // TODO:  Return a different brush if the default is not desired
 switch (nCtlColor)
 {
 case CTLCOLOR_BTN:
 case CTLCOLOR_STATIC:
  pDC->SetBkMode(TRANSPARENT);
  pDC->SetTextColor(RGB(200, 255, 255));
 case CTLCOLOR_DLG:
  return (HBRUSH)m_brBack.GetSafeHandle();
 }
 return hbr;
}
           

    但是對于按鈕來說上述方法是不起作用的,隻能尋找其他解決辦法。實際上,要想改變按鈕的背景色和文本顔色,需要使用CButton類的一個成員函數DrawItem,該函數的聲明如下:// Overridables (for owner draw only)

 virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);

    這個函數是一個虛函數,當一個自繪制按鈕在繪制時候,架構将會調用這個虛函數。是以,如果想要實作一個自繪按鈕,就應該重載這個虛函數。該函數的參數是DRAWITEMSTRUCT結構類型,定義如下:

typedef struct tagDRAWITEMSTRUCT {
    UINT        CtlType;
    UINT        CtlID;
    UINT        itemID;
    UINT        itemAction;
    UINT        itemState;
    HWND        hwndItem;
    HDC         hDC;
    RECT        rcItem;
    ULONG_PTR   itemData;
} DRAWITEMSTRUCT;
           

    該結構體有一個hDC成員,指向将要繪制的按鈕的DC。為了繪制這個按鈕,可以向該DC中選入自定義的顔色、畫刷等對象。了解了這些知識,下面開始真正的自繪按鈕,首先看一下效果,效果如下:

MFC自定義按鈕的實作

    這裡列出了按鈕的幾種狀态,第一種是正常狀态,第二種是按下之後的狀态,第三中是不同的背景顔色,這裡設定了兩種不同的背景顔色,實際上這裡的背景是圖檔,是以看起來會有立體的感覺!

以下是.h檔案源代碼,看起來應該不難^_^:

class CPicButton : public CButton
{
public:
 CPicButton(void);
 ~CPicButton(void);

public:
 enum { MAX_BTN_COLOR = 3 };
 enum { COLOR_NORMAL = 0, COLOR_FOCUSED = 1, COLOR_PRESSED = 2 }; //按鈕的三種狀态

 COLORREF m_crForeColor[MAX_BTN_COLOR];

 UINT  m_nTypeStyle;
 int   m_nColorType;

 BOOL  m_bMouseOnButton; // Is mouse over the button?
 BOOL  m_bIsPressed;   // Is button pressed?
 BOOL  m_bIsFocused;  // Is button focused?
 BOOL  m_bIsDisabled;  // Is button disabled?

 CString  m_sFontName; //字型名字
 int   m_nFontWidth, m_nFontHeight; //字型大小
 BOOL  m_bPointFont;

public:
 virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); //這裡是重點,通過調用Invalidate()可以馬上重繪按鈕,進而調用這個函數
 virtual BOOL PreTranslateMessage(MSG *pMsg);

 void SetForeColor(int nIndex, COLORREF color, BOOL bRepaint = TRUE);//設定前景色
 void SetColorType(int nColorIdx);                           //設定背景顔色的類别,在這裡代表兩種背景圖檔
 void SetCaptionFont(CString sFontName, int nWidth, int nHeight, BOOL bPointFont=FALSE);

protected:
 virtual void PreSubclassWindow();//在這裡面修改按鈕的樣式
 virtual void DrawBackground(CDC *dc, int nWidth, int nHeight);//繪制背景
 virtual void DrawBtnCaption(CDC *dc, int nWidth, int nHeight);//繪制字型

private:
 void FreeResources();
 void CancelHover();

public:
 afx_msg BOOL OnClicked();//點選事件
 afx_msg void OnKillFocus(CWnd* pNewWnd);
 afx_msg void OnMouseMove(UINT nFlags, CPoint point);//滑鼠移動事件
 afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized);
 afx_msg LRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam);

DECLARE_MESSAGE_MAP()
};
           

下面是.cpp檔案源碼:

#include "stdafx.h"
#include "PicBtn.h"
#include "DrawingTools.h"
#include "Resource.h"
CPicButton::CPicButton(void)
{
 m_crForeColor[COLOR_NORMAL]  = RGB(0, 0, 0);
 m_crForeColor[COLOR_FOCUSED] = RGB(240, 240, 240);
 m_crForeColor[COLOR_PRESSED] = RGB(255, 255, 255);
 m_nColorType  = 0;  // Brown or Red
 m_bMouseOnButton = FALSE;
 m_bIsPressed  = FALSE;
 m_bIsFocused  = FALSE;
 m_bIsDisabled  = FALSE;
 m_nFontHeight  = 17;
 m_nFontWidth  = 6;
 m_bPointFont  = FALSE;
 m_sFontName   = _T("Arial");
}
CPicButton::~CPicButton(void)
{
 FreeResources();
}
void CPicButton::FreeResources()
{
}
void CPicButton::CancelHover()
{
 if (m_bMouseOnButton)
 {
  m_bMouseOnButton = FALSE;
  Invalidate();
 }
}
            
void CPicButton::PreSubclassWindow()
{
#if defined(_CE_VERSION)
 ModifyStyle(0, BS_OWNERDRAW, SWP_FRAMECHANGED);
#else
 UINT nBS = GetButtonStyle();
                     
 m_nTypeStyle = nBS & BS_TYPEMASK;
 if (m_nTypeStyle == BS_DEFPUSHBUTTON)
 {
  m_nTypeStyle = BS_PUSHBUTTON;
 }
// You should not set the Owner Draw before this call 
 // (don't use the resource editor "Owner Draw" or 
 // ModifyStyle(0, BS_OWNERDRAW) before calling PreSubclassWindow())
 ASSERT(m_nTypeStyle != BS_OWNERDRAW);
//Switch to owner-draw
 ModifyStyle(BS_TYPEMASK, BS_OWNERDRAW, SWP_FRAMECHANGED);
#endif
 CButton::PreSubclassWindow();
}
BOOL CPicButton::PreTranslateMessage(MSG* pMsg)
{
 if (pMsg->message == WM_LBUTTONDBLCLK)
  pMsg->message = WM_LBUTTONDOWN;
 return CButton::PreTranslateMessage(pMsg);
}
void CPicButton::SetColorType(int nColorIdx)
{
 m_nColorType = nColorIdx;
}
void CPicButton::SetCaptionFont(CString sFontName, int nWidth, int nHeight, BOOL bPointFont)
{
 m_sFontName  = sFontName;
 m_nFontWidth = nWidth;
 m_nFontHeight = nHeight;
 m_bPointFont = bPointFont;
 Invalidate();
}
BEGIN_MESSAGE_MAP(CPicButton, CButton)
 ON_WM_KILLFOCUS()
 ON_CONTROL_REFLECT_EX(BN_CLICKED, OnClicked)  //消息反射
 ON_WM_ACTIVATE()
#ifndef _CE_VERSION
 ON_WM_MOUSEMOVE()
 ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
#endif
END_MESSAGE_MAP()
#ifndef _CE_VERSION
void CPicButton::OnMouseMove(UINT nFlags, CPoint point)
{
 CWnd   *wndUnderMouse = NULL;
 CWnd   *wndActive = this;
 TRACKMOUSEEVENT csTME;
 CButton::OnMouseMove(nFlags, point);
 ClientToScreen(&point);
 wndUnderMouse = WindowFromPoint(point);//獲得點下面的元件
 // If the mouse enter the button with the left button pressed then do nothing
 if (nFlags & MK_LBUTTON && m_bMouseOnButton == FALSE)
  return;
 if (wndUnderMouse && wndUnderMouse->m_hWnd == m_hWnd && wndActive)
 {
  if (!m_bMouseOnButton)
  {
   m_bMouseOnButton = TRUE;
   Invalidate();
   csTME.cbSize = sizeof(csTME);
   csTME.dwFlags = TME_LEAVE;
   csTME.hwndTrack = m_hWnd;
   ::_TrackMouseEvent(&csTME);//發送滑鼠離開的消息,見我之前的部落格
  }
 }
 else
  CancelHover();
}
LRESULT CPicButton::OnMouseLeave(WPARAM wParam, LPARAM lParam)
{
 CancelHover();
 return 0;
}
#endif
BOOL CPicButton::OnClicked()
{ 
 SetFocus();//設定焦點
 Invalidate();
 return FALSE;
}
void CPicButton::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
 CDC   *pDC = CDC::FromHandle(lpDIS->hDC); //擷取CDC
 int   nWidth, nHeight;
 CRect  itemRect = lpDIS->rcItem;
  nWidth = itemRect.Width();
 nHeight = itemRect.Height();
 m_bIsPressed = (lpDIS->itemState & ODS_SELECTED);//是不是已經按下
 m_bIsFocused = (lpDIS->itemState & ODS_FOCUS);   //是不是獲得了焦點
 m_bIsDisabled = (lpDIS->itemState & ODS_DISABLED); //是不是沒使能
 pDC->SetBkMode(TRANSPARENT);//設定背景透明
 DrawBackground(pDC, nWidth, nHeight);//畫背景
 DrawBtnCaption(pDC, nWidth, nHeight); //畫字型
}
void CPicButton::DrawBackground(CDC *dc, int nWidth, int nHeight)
{
 if (m_bIsPressed)
 {
  if (m_nColorType == 0)
   ::DrawBMP(dc,IDB_BTN_YP, 0, 0); //
  else
   ::DrawBMP(dc,IDB_BTN_RP, 0, 0);//畫紅色按下背景
 }
 else
 {
  if (m_nColorType == 0)
   ::DrawBMP(dc,IDB_BTN_Y, 0, 0);
  else
   ::DrawBMP(dc,IDB_BTN_R, 0, 0);//畫紅色背景
 }
}
void CPicButton::DrawBtnCaption(CDC *dc, int nWidth, int nHeight)
{
 CString   str;
 CRect   rect;
 int    nColorIndex;
 CFont   font, *old_font;
 if (m_bIsPressed)
  nColorIndex = COLOR_PRESSED;
 else if (m_bMouseOnButton)
  nColorIndex = COLOR_FOCUSED;
 else
  nColorIndex = COLOR_NORMAL;
 if (m_bPointFont)
  font.CreatePointFont(m_nFontHeight * 10, m_sFontName);
 else
 {
  font.CreateFont(m_nFontHeight, m_nFontWidth, 0, 0, FW_BOLD, 0, 0, 0,
   DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_DEFAULT_PRECIS,
   DEFAULT_QUALITY, FIXED_PITCH | FF_MODERN, m_sFontName);
 }
 GetWindowText(str);
 rect = CRect(2, 2, nWidth - 4, nHeight - 4);
 old_font = dc->SelectObject(&font);
 dc->SetTextColor(m_crForeColor[nColorIndex]);
 //顯示字型
 dc->DrawText(str, str.GetLength(), &rect, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
 dc->SelectObject(old_font);
 font.DeleteObject();
}
void CPicButton::SetForeColor(int nIndex, COLORREF crColor, BOOL bRepaint)
{
 m_crForeColor[nIndex] = crColor;
 if (bRepaint) 
  Invalidate();
}
void CPicButton::OnKillFocus(CWnd * pNewWnd)
{
 CButton::OnKillFocus(pNewWnd);
 CancelHover();//當失去焦點的時候,需要改變字型顔色,需要重繪,調用Invalidate();
}
void CPicButton::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
 CButton::OnActivate(nState, pWndOther, bMinimized);
 if (nState == WA_INACTIVE)  //當失去焦點的時候,需要改變字型顔色,需要重繪,調用Invalidate();
  CancelHover();
}
           
自定義按鈕類已經寫好了,那麼怎麼和對話框中的按鈕關聯起來呢?方法如下:
           
在.h中:

#include "PicBtn.h"

CPicButton m_btnExit;

在.cpp中:

void CFiveChessDlg::DoDataExchange(CDataExchange* pDX)
{
 CDialogEx::DoDataExchange(pDX);
 //這裡就把我們對話框中的按鈕和我們自定義的按鈕類關聯起來了
 DDX_Control(pDX, IDC_BUTTON_Quit, m_btnExit);
}

//設定按鈕的顯示字型,背景顔色等等,這個函數可以放在OnInitDialog函數裡面調用

void CFiveChessDlg::InitButton(){

 m_btnExit.SetCaptionFont(_T("SimHei"), 0, 12, TRUE);
 m_btnExit.SetWindowText(_T("退出"));
 m_btnExit.SetColorType(1);

}
           

       然後就和正常的按鈕一樣響應點選事件就可以了!

    通過以上自定義按鈕的方法進行擴充,可以做出我們需要的效果,比如在按鈕上顯示圖檔和文字或者就純粹的圖檔按鈕等等,QQ界面就用了很多的圖檔按鈕!

    下面是我自定義的另外一種按鈕:

MFC自定義按鈕的實作

      這個就比較簡單了,代碼和之前的那個圖檔背景的差不多,隻是它這個背景有三種顔色,而之前的是字型有三種顔色而已!

    順便講一下上面這個對話框頂部尖尖的地方以及按鈕等間隔和等大小是怎麼實作的,代碼如下:

//按鈕等間隔和等大小的實作

void CSetting_Menu::UpdateLayout()
{
 CRect rect1,rect2;
 rect1.top = 0;
 rect1.left = 0;
 rect1.right = rect1.left + 250;
 rect1.bottom = rect1.top + 315;
 MoveWindow(rect1);//設定對話框的大小,固定的
 if (GetDlgItem(IDC_BUTTON_General_Setting))
 {
  rect1.left = 5;
  rect1.right = rect1.left+240;
  rect1.top = 15;
  rect1.bottom = rect1.top + 45;
  GetDlgItem(IDC_BUTTON_General_Setting)->MoveWindow(rect1); //移動按鈕到這個位置
 }
 if (GetDlgItem(IDC_BUTTON_COM_Port_Setting))
 {
  rect1.left = 5;
  rect1.right = rect1.left + 240;
  rect1.top = 5*4+45;
  rect1.bottom = rect1.top + 45;
  GetDlgItem(IDC_BUTTON_COM_Port_Setting)->MoveWindow(rect1);
 }
 if (GetDlgItem(IDC_BUTTON_DataFile_Setting))
 {
  rect1.left = 5;
  rect1.right = rect1.left + 240;
  rect1.top = 5*5 + 45*2;
  rect1.bottom = rect1.top + 45;
  GetDlgItem(IDC_BUTTON_DataFile_Setting)->MoveWindow(rect1);
 }
 if (GetDlgItem(IDC_BUTTON_General_Command))
 {
  rect1.left = 5;
  rect1.right = rect1.left + 240;
  rect1.top = 5*6 + 45*3;
  rect1.bottom = rect1.top + 45;
  GetDlgItem(IDC_BUTTON_General_Command)->MoveWindow(rect1);
 }
 if (GetDlgItem(IDC_BUTTON_AutoClib_Setting))
 {
  rect1.left = 5;
  rect1.right = rect1.left + 240;
  rect1.top = 5*7 + 45*4;
  rect1.bottom = rect1.top + 45;
  GetDlgItem(IDC_BUTTON_AutoClib_Setting)->MoveWindow(rect1);
 }
 if (GetDlgItem(IDC_BUTTON_ACC_GYRO_Scale))
 {
  rect1.left = 5;
  rect1.right = rect1.left + 240;
  rect1.top = 5*8 + 45*5;
  rect1.bottom = rect1.top + 45;
  GetDlgItem(IDC_BUTTON_ACC_GYRO_Scale)->MoveWindow(rect1);
 }
}
           
以上代碼就是對對話框的進行了一個布局,這樣就看起來比較規整,按鈕的大小和間隔都是一樣的!

//對話框頂部凸起部分的實作

CPoint pts[7] = { CPoint(0, 10), CPoint(40, 10), CPoint(50, 0), CPoint(60, 10), CPoint(250, 10), CPoint(250, 315), CPoint(0, 315)};

//建立一個多邊形區域,以上面定義的七個頂點為邊界
 HRGN poloy = CreatePolygonRgn(pts, 7, ALTERNATE);
 CRgn rgn;
 rgn.Attach(poloy);
 SetWindowRgn(rgn,TRUE); //設定視窗的多邊形形狀
 rgn.Detach();

以上代碼就可以把對話框設定成各種形狀了,是不是很Cool ^_^。