先看一個簡單的例子:讓程式對滑鼠左鍵響應一個messagebox。
有兩種方法完成這個任務:1.在對應的類上選擇Add windows message Handler,然後選擇WM_LBUTTONDOWN訊息,然後增加處理函數,在處理函數中添加一句:
MessageBox("view click!");
或者使用菜單上的查看->建立類嚮導,在message maps下選擇:Project選擇這個工程,Class Name選擇從CXXVew,Object ID選擇這個類,訊息選擇WM_LBUTTONDOWN,然後增加函數即可。
注意,不論是使用ClassWizard還是使用Add windows message Handler,都會在3個地方增加代碼:
1.類的標頭檔中:
//{{AFX_MSG(CCH_4_DRAWView)afx_msg void OnLButtonDown(UINT nFlags, CPoint point);//}}AFX_MSG
其中AFX_msg稱為注釋宏,它們之間的程式顯示為灰色,說明他們之間聲明的是訊息響應函數。
2.類的源檔案中增加訊息映射宏:
BEGIN_MESSAGE_MAP(CCH_4_DRAWApp, CWinApp)//{{AFX_MSG_MAP(CCH_4_DRAWApp)ON_COMMAND(ID_APP_ABOUT, OnAppAbout)// NOTE - the ClassWizard will add and remove mapping macros here.// DO NOT EDIT what you see in these blocks of generated code!//}}AFX_MSG_MAP// Standard file based document commandsON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)ON_COMMAND(ID_FILE_OPEN, CWinApp::OnFileOpen)// Standard print setup commandON_COMMAND(ID_FILE_PRINT_SETUP, CWinApp::OnFilePrintSetup)END_MESSAGE_MAP()
3.訊息響應函數
void CCH_4_DRAWView::OnLButtonDown(UINT nFlags, CPoint point) {// TODO: Add your message handler code here and/or call defaultMessageBox("view click!");CView::OnLButtonDown(nFlags, point);}
所以,如果需要刪除某些內容,最好是通過在對應的訊息處理函數上選擇delete,而不是手動刪除。
看到上面的代碼,我們不禁會問:在SDK下,使用的是switch……case語句,含義是很明確的,而在這裡訊息映射到底是如何?的呢?
有兩種可選的機制:
1.使用虛函數:在基類中對每個訊息處理函數定義一個虛函數,在衍生類別中可以重寫這些函數。但是,這種方法是不可取的,因為我們知道,虛函數時要通過虛函數表來實現的,而在MFC如此繁雜的繼承派生體系中,為每一個類增加一個虛函數表會大大增加整個程式的開銷,而且掃描虛函數表會消耗很多的時間。
2.採用訊息映射表。對於每個·可以接收和處理訊息的類,定義一個訊息和訊息函數的靜態對照表。當有訊息到來時,只要掃描這個表就可以判斷這個類是否能夠處理該訊息。如果子類找不到訊息響應函數,則交給基類處理。
下面我們通過簡單的畫圖的例子來說明訊息映射,首先回顧一下SDK下的繪圖。有3種基本的方法:
1.使用SetPixel來為某一像素設定顏色,使用GetPixel來擷取某一點的顏色。
2.使用MoveToEx來移動到某一點,使用LineTo來從當前點畫到指定點。
3.使用windows提供的一些基本畫圖函數,比如Rectangle、Ellipse、RoundRect等等。
而與畫圖相關的1個重要的概念是DC(裝置描述表、裝置上下文),還有對應的控制代碼HDC。在MFC中,我們還是會用到它們。
言歸正傳,開始畫圖:先從畫線開始:
我們發現OnLButtonDown函數回傳給我們一個CPoint類型的對象,它表明了滑鼠按下去的點的具體位置。所以我們可以為自己的view類添加一個CPoint的成員m_ptOrigin來儲存這個點(注意,習慣上,我們對成員變數,都以m_開頭)。
void CCH_4_DRAWView::OnLButtonDown(UINT nFlags, CPoint point) {// TODO: Add your message handler code here and/or call default//MessageBox("view click!");m_ptOrigin = point;CView::OnLButtonDown(nFlags, point);}
當滑鼠左鍵彈起時,獲得終點座標。為此,我們要為WM_LBUTTONUP添加響應函數。此時,我們既可以使用Plantform SDK下的傳統做法,也可以使用MFC下的作法。
void CCH_4_DRAWView::OnLButtonUp(UINT nFlags, CPoint point) {// TODO: Add your message handler code here and/or call defaultHDC hdc;hdc = ::GetDC(m_hWnd);MoveToEx(hdc,m_ptOrigin.x,m_ptOrigin.y,NULL);LineTo(hdc,point.x,point.y);::ReleaseDC(m_hWnd,hdc);CView::OnLButtonUp(nFlags, point);}
注意,在我們的view類的基類CView的基類CWnd中,已經定義了public成員HWND m_hWnd;這裡是需要正常調用即可。
下面看MFC的方法。MFC對裝置描述表進行了封裝,變成了CDC具體用法如下:
CDC* pDC = GetDC();pDC->MoveTo(m_ptOrigin);pDC->LineTo(point);ReleaseDC(pDC);
這裡需要定義一個指向CDC的指標,然後調用GetDC()函數獲得當前裝置上下文的指標,然後通過成員函數畫線。我們發現,由於將m_hWnd設為成員變數,所以我們不需要再函數中把這個變數傳遞進來了。
這裡,我們只能在view裡面畫圖。與SDK下的程式相似,我們的畫圖可以寬展到整個客戶區、整個視窗、甚至整個螢幕。這取決於你獲得的DC是什麼樣的:
CClientDC dc(this);dc.MoveTo(m_ptOrigin);dc.LineTo(point);
注意,這裡並沒有使用類似於GetDC相關的操作,也沒有ReleaseDC,這是因為它們都是在這個類的構造和解構函式中自動完成的。這是一種“以對象管理資源”的思路,它在一定程度上可以避免在ReleaseDC之前發生了異常而導致記憶體泄露的問題。因為異常一旦發生,dc會被釋放,釋放時調用解構函式就能回收資源。
如何想將線畫到工具列上,那麼必須獲得架構視窗的控制代碼:
CClientDC dc(GetParent());
為了畫到整個視窗上,需要使用CWindow 類:
CWindowDC dc(GetParent());
如果想畫到整個案頭上上,那麼需要獲得案頭的DC:
CWindowDC dc(GetDesktopWindow());
雖然畫出的位置並不是太令人滿意,但是聊勝於無吧。
下面我們看看如何修改畫筆的顏色。這裡的思路與SDK下是一樣的,定義一根畫筆選定它的顏色,粗細之類的,然後把它選入裝置描述表中即可。
CClientDC dc(this);CPen pen(PS_SOLID,1,RGB(255,0,0));CPen* pOldPen = dc.SelectObject(&pen);dc.MoveTo(m_ptOrigin);dc.LineTo(point);dc.SelectObject(pOldPen);
注意,習慣上,當我們畫完線之後,需要將線性、顏色恢複到之前的狀態。
下面介紹如何使用刷子。
CBrush brush(RGB(255,0,0));CClientDC dc(this);dc.FillRect(CRect(m_ptOrigin,point),&brush);
首先要定義一個刷子類型的對象,擷取裝置上下文之後,使用FillRect函數來使用刷子,這個函數接受兩個參數,第一個參數是指向矩形的指標,第二個參數是指向刷子的指標。但是程式中使用的第一個參數卻是利用兩個點構造出來的矩形,並沒有取地址,這是為什麼呢?原因在於CRect類重載了LPCRECT( )操作符,這裡就可以自動調用這個重載函數,而不用取地址了。
刷子還有一種建構函式,接受指向位元影像的指標:CBrush( CBitmap* pBitmap );我們可以建立一幅位元影像,然後通過裝載位元影像,使用位元影像構造刷子。
CBitmap bitmap;bitmap.LoadBitmap(IDB_BITMAP1);CBrush brush(&bitmap);CClientDC dc(this);dc.FillRect(CRect(m_ptOrigin,point),&brush);
需要注意的是,當我們的白色畫刷並不是透明的,會出現相互遮擋的情況,如果我們想畫出透明的矩形,就要費一番周折了。因為CBrush類裡面並沒有提供透明畫刷的函數。但是我們知道,在GetStockObject函數中,可以選擇HOLLOW_BRUSH或者NULL_BRUSH來表示一個空心的刷子。那麼怎麼把它們結合起來呢?
CClientDC dc(this);CBrush *pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));CBrush *pOldBrush = dc.SelectObject(pBrush);dc.Rectangle(CRect(m_ptOrigin,point));dc.SelectObject(pOldBrush);
這裡面調用了FromHandle函數,他將一個Windows GDI下的brush,轉化成了指向CBrush的指標。值得注意的是,這個函數是靜態函數,所以是不是通過對象調用的。
最後,我們對Rectangle函數和FillRect函數做一個簡單的對比,它們都是畫矩形的,但是由於FillRect函數需要指明使用的刷子是哪個,所以做之前不需要使用SelectObject函數來確定畫刷。
下面我們看看如何繪製連續的線條。
這個問題的關鍵在於捕獲WM_MOUSEMOVE訊息,但並不是只要滑鼠移動就畫線,你希望的肯定是滑鼠按下去以後開始畫線,然後鬆開以後停止畫線。你需要一個記錄滑鼠是否被按下的變數,在WM_LBUTTONDOWN記錄滑鼠被按下,然後WM_LBUTTONUP時記錄滑鼠彈起。在處理WM_MOUSEMOVE訊息的函數時,先判斷一下滑鼠被按下的那個變數是否為真,如果是才畫圖。我們將這個變數起名為m_bDraw,並在建構函式中初始化為FALSE。那麼我們響應WM_MOUSEMOVE訊息的程式就是:
void CCH_4_DRAWView::OnMouseMove(UINT nFlags, CPoint point) {// TODO: Add your message handler code here and/or call defaultCClientDC dc(this);if(TRUE == m_bDraw){dc.MoveTo(m_ptOrigin);dc.LineTo(point);m_ptOrigin = point;}CView::OnMouseMove(nFlags, point);}