導讀:
我並不推薦採用自繪的方式去完成一些控件(比如CStatic,CButton,RadioBox,CheckBox等)的美化,而是推薦大家從CWnd入手,把這些基本控件完全重新繪製一遍(當然,有些做的很好的控件還是需要繼承來自繪的,比如CListCtrl)。爲什麼這麼做?因爲MFC對這些控件的某些操作是隱蔽的,某些限制是我們無法接受的(比如CTabCtrl的頭部高度和每個Item的寬度)。我覺得掌握如下知識,繪製其他基本控件就不是繪製的問題,而是數據結構的事情了。
頭文件:
#ifndef QCTRL_H
#define QCTRL_H
#include <afxwin.h>
class QMemDC : // 我把雙緩存封裝到類中,這樣就方便多了
public CDC
{
private:
CDC* dcSrc;
CRect rect;
CBitmap bmp;
public:
QMemDC(CDC* dc,CRect rc);
void Apply();
};
class QCtrl :
public CWnd
{
protected:
CString szClassName;
bool isMouseIn;
bool isPressed;
public:
QCtrl();
~QCtrl();
bool Create(CWnd* pParent,CRect rc,CString text,DWORD id = 0,DWORD style = WS_VISIBLE|WS_CHILD);
protected:
void PostClickEvent();
protected:
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnMouseHover(UINT nFlags, CPoint point);
afx_msg void OnMouseLeave();
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnPaint();
public:
DECLARE_MESSAGE_MAP()
};
#endif
我們需要的基本上就是這幾個消息了。
實現文件:
#include "QCtrl.h"
// QMemDC
QMemDC::QMemDC(CDC* dc,CRect rc)
{
dcSrc = dc;
rect = rc;
// 創建內存DC
CreateCompatibleDC(dc);
bmp.CreateCompatibleBitmap(dc,rc.Width(),rc.Height());
SelectObject(bmp);
}
void QMemDC::Apply()
{
// 將內存DC繪製到設備DC上
dcSrc->BitBlt(rect.left,rect.top,rect.Width(),rect.Height(),this,0,0,SRCCOPY);
}
// QCtrl
QCtrl::QCtrl()
{
isMouseIn = false;
isPressed = false;
// 註冊控件類
szClassName = AfxRegisterWndClass(0);
}
QCtrl::~QCtrl()
{
}
bool QCtrl::Create(CWnd* pParent,CRect rc,CString text,DWORD id /* = 0 */,DWORD style /* = WS_VISIBLE|WS_CHILD */)
{
// 動態創建控件
BOOL ret = CWnd::CreateEx(0,szClassName,text,style,rc,pParent,id);
return ret ? true : false;
}
void QCtrl::PostClickEvent()
{
// 該函數用來向父窗口發送 單擊 消息
CWnd* parent = GetParent();
if(parent != NULL)
{
WPARAM wp = MAKEWPARAM(GetDlgCtrlID(),BN_CLICKED);
LPARAM lp = (LPARAM) m_hWnd;
parent->PostMessage(WM_COMMAND,wp,lp);
}
}
BEGIN_MESSAGE_MAP(QCtrl, CWnd)
ON_WM_MOUSEMOVE()
ON_WM_MOUSEHOVER() // 此消息系統並不會給我們發送
ON_WM_MOUSELEAVE()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_PAINT()
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
// 鼠標進入和鼠標移出消息需要我們自己監聽
void QCtrl::OnMouseMove(UINT nFlags, CPoint point)
{
// 只處理鼠標第一次進入時的情況
if(!isMouseIn)
{
isMouseIn = true;
TRACKMOUSEEVENT evt = { sizeof(evt), TME_LEAVE, m_hWnd, 0 };
TrackMouseEvent(&evt);
OnMouseHover(0,CPoint());
}
}
void QCtrl::OnMouseHover(UINT nFlags, CPoint point)
{
// 鼠標進入
Invalidate();
}
void QCtrl::OnMouseLeave()
{
// 鼠標離開
isMouseIn = false;
isPressed = false;
Invalidate();
}
void QCtrl::OnLButtonDown(UINT nFlags, CPoint point)
{
// 鼠標按下
isPressed = true;
Invalidate();
}
void QCtrl::OnLButtonUp(UINT nFlags, CPoint point)
{
// 鼠標鬆開
if(isPressed)
{
isPressed = false;
Invalidate();
PostClickEvent();
}
}
BOOL QCtrl::OnEraseBkgnd(CDC* pDC)
{
return TRUE; // 阻止擦除背景,防止閃爍
}
void QCtrl::OnPaint()
{
CPaintDC dc(this);
CRect rc;
GetClientRect(&rc);
// 採用雙緩存,防止閃爍
QMemDC mdc(&dc,rc);
// 刷背景
COLORREF bkgnd = RGB(100,0,0);
if(isMouseIn)
{
if(isPressed)
bkgnd = RGB(250,0,0);
else
bkgnd = RGB(180,0,0);
}
mdc.FillSolidRect(&rc,bkgnd);
// 設置文字字體
CFont font;
font.CreatePointFont(110,"宋體"); // 11號字體,該參數與實際字體號有10倍的關係
mdc.SelectObject(font);
// 獲取文字
CString text;
GetWindowText(text);
// 設置文字屬性
mdc.SetBkMode(TRANSPARENT);
mdc.SetTextColor(RGB(0,0,0));
// 繪製文本
DWORD style = DT_SINGLELINE | DT_VCENTER | DT_CENTER; // 文本格式:單行+水平居中+垂直居中
mdc.DrawText(text,-1,&rc,style); // 更多文本顯示格式可參考百度百科DrawText說明
// 使繪製生效
mdc.Apply();
}
如果上升到界面庫設計的高度,這裏的OnPaint函數應該這麼寫:
爲QCtrl添加一個虛函數virtual void DoPaint(QMemDC &dc,CRect rc);
CPaintDC dc(this);
CRect rc;
GetClientRect(&rc);// 採用雙緩存,防止閃爍
QMemDC mdc(&dc,rc);
DoPaint(mdc,rc);
如此,子類繼承QCtrl只需要重寫該函數即可。
由於我們不是子類化,所以只能動態創建:
在CXXDlg.h添加變量QCtrl ctrl;
在OnInitDialog中ctrl.Create(this,CRect(10,10,210,30),"Nice Work"); //此處id和style是缺省參數,當我們指定一個ID後,就可以在CXXDlg的消息映射ON_BK_CLICKED函數中接收到該控件的單擊事件了。