MFC 繪圖核心在於取得裝置內容(DC)並呼叫 GDI 函式。本文以「滑鼠拖曳畫直線」為例,說明 WM_LBUTTONDOWN/WM_LBUTTONUP/WM_MOUSEMOVE 訊息處理,並比較四種取得 DC 的方式優缺點。
// View 類別成員
CPoint m_ptOrigin; // 起始點
BOOL m_bDrawing; // 是否正在繪圖
// 滑鼠按下:記錄起點
void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
{
m_ptOrigin = point;
m_bDrawing = TRUE;
SetCapture(); // 捕捉滑鼠,確保 MouseMove/Up 仍送達此視窗
CView::OnLButtonDown(nFlags, point);
}
// 滑鼠移動:連續畫線
void CMyView::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_bDrawing) {
CClientDC dc(this); // 即時繪製到螢幕
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
m_ptOrigin = point; // 更新起點為上一終點
}
CView::OnMouseMove(nFlags, point);
}
// 滑鼠釋放:結束繪圖
void CMyView::OnLButtonUp(UINT nFlags, CPoint point)
{
if (m_bDrawing) {
m_bDrawing = FALSE;
ReleaseCapture();
// 這裡可將最終線段存入資料結構,以便重繪
}
CView::OnLButtonUp(nFlags, point);
}
| 方式 | 程式碼 | 特點 | 適用場景 |
|---|---|---|---|
| 1. 全域 API | HDC hdc = ::GetDC(m_hWnd); ... ::ReleaseDC(m_hWnd, hdc); | 最底層,需手動管理釋放 | 非 MFC 專案、特殊需求 |
| 2. CDC::GetDC/ReleaseDC | CDC* pDC = GetDC(); ... ReleaseDC(pDC); | MFC 封裝,指標需手動釋放 | 短暫繪圖、回呼函式中 |
| 3. CClientDC (RAII) | CClientDC dc(this); // 建構時 GetDC,解構時 ReleaseDC | **推薦**:自動管理生命週期,僅限客戶區 | OnMouseMove、OnPaint 外的即時繪圖 |
| 4. CWindowDC | CWindowDC dc(this); // 含非客戶區(標題列、邊框) | 可繪製整個視窗區域 | 非客戶區繪圖、桌面視窗 |
| 5. CPaintDC | CPaintDC dc(this); // 僅限 OnPaint,自動 BeginPaint/EndPaint | **唯一合法**用於 WM_PAINT 回應 | OnPaint 重繪 |
OnMouseMove 中用 CClientDC 直接畫到螢幕,使用者拖曳時看到線條跟隨。CArray<LINE>,OnPaint 中遍歷陣列重繪所有線條,確保視窗最小化/遮蔽後恢復正確。
struct LINE { CPoint ptFrom, ptTo; };
CArray m_lines;
void CMyView::OnLButtonUp(UINT nFlags, CPoint point)
{
if (m_bDrawing) {
m_bDrawing = FALSE;
ReleaseCapture();
LINE ln; ln.ptFrom = m_ptOrigin; ln.ptTo = point;
m_lines.Add(ln);
Invalidate(); // 觸發 WM_PAINT 重繪
}
CView::OnLButtonUp(nFlags, point);
}
void CMyView::OnDraw(CDC* pDC)
{
for (int i = 0; i < m_lines.GetSize(); ++i) {
pDC->MoveTo(m_lines[i].ptFrom);
pDC->LineTo(m_lines[i].ptTo);
}
}
參考《VC++ 雙緩衝繪圖消除閃爍》一文,在 OnDraw 使用 CMemDC(或自行建立相容 DC/點陣圖)一次性 BitBlt 到螢幕。