完整複刻 Windows 畫圖板核心功能:自由筆刷、直線/矩形/橢圓、文字輸入、圖塊拖放、多級撤銷重做、畫面縮放、檔案讀存、Base64 圖片介面、工具箱雙欄、調色盤。架構採用 CScrollView + 文檔/視圖,繪圖物件導向設計,適合作為 MFC 繪圖應用教學範本。
class CDrawObject : public CObject { // 基類
virtual void Draw(CDC* pDC) = 0;
CRect m_rect; // 外接矩形
};
class CDrawPen : public CDrawObject { // 筆/刷 - 手繪折線
CArray m_points;
int m_nWidth; COLORREF m_color;
};
class CDrawShape : public CDrawObject { // 幾何圖形
int m_nShapeType; // 直線/矩形/橢圓...
int m_nWidth; COLORREF m_color; BOOL m_bFilled;
};
class CDrawText : public CDrawObject { // 文字
CString m_str; LOGFONT m_lf; COLORREF m_color;
};
文檔類 CDrawDoc 持有 CObList m_objects 管理所有圖元。
// 1. 禁止系統擦除背景
BOOL CMyView::OnEraseBkgnd(CDC* pDC) { return TRUE; }
// 2. OnDraw 先畫到記憶體 DC
void CMyView::OnDraw(CDC* pDC)
{
CMemDC memDC(pDC, &rectClip); // RAII 記憶體 DC 包裝類
memDC.FillSolidRect(rectClip, RGB(255,255,255)); // 白底
for (pos = m_objects.GetHeadPosition(); pos; )
m_objects.GetNext(pos)->Draw(&memDC);
// 尚未確認的暫時圖元 (滑鼠拖曳中) 另行繪製
if (m_pTempObj) m_pTempObj->Draw(&memDC);
// CMemDC 解構時自動 BitBlt 回螢幕
}
| 事件 | 處理邏輯 |
|---|---|
OnLButtonDown | 建立對應圖元物件 (根據當前工具),記錄起始點,加入暫時物件指標 |
OnMouseMove | 更新暫時物件幾何資料 (終點/矩形/路徑),Invalidate() 觸發重繪 |
OnLButtonUp | 最終化暫時物件,移入文檔正式列表,清空暫時指標,推入撤銷堆疊 |
class CUndoStack {
CArray m_history; // 歷史畫面快照 (位圖句柄)
int m_top; // 當前指標
public:
void PushSnapshot(CDC* pDC, CRect rect) {
// 截取當前畫面存入位圖
if (m_top < m_history.GetSize()-1) m_history.RemoveAt(m_top+1, m_history.GetSize()-m_top-1);
// ... 建立相容位圖、BitBlt 複製 ...
m_history.Add(hBmp); m_top++;
}
HBITMAP Undo() { if (m_top > 0) return m_history[--m_top]; return NULL; }
HBITMAP Redo() { if (m_top < m_history.GetSize()-1) return m_history[++m_top]; return NULL; }
};
策略:每次筆劃結束存一張全畫面快照。簡單但記憶體隨畫面大小/步數增長。大型專案建議改用 命令模式 (記錄動作而非影像)。
CScrollView 預設 MM_TEXT 不支援 SetViewportExt/WindowExt。解法:複製 MFC 原始碼 CScrollView::SetScrollSizes 修改為 MM_ISOTROPIC,封裝為 CADMMScrollView。
void CADMMScrollView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
CScrollView::OnPrepareDC(pDC, pInfo);
pDC->SetMapMode(MM_ISOTROPIC);
int xMm = pDC->GetDeviceCaps(HORZSIZE);
int yMm = pDC->GetDeviceCaps(VERTSIZE);
int xPix = pDC->GetDeviceCaps(HORZRES);
int yPix = pDC->GetDeviceCaps(VERTRES);
pDC->SetWindowExt(xMm * 100, yMm * 100);
pDC->SetViewportExt((int)(xPix * m_fZoom), (int)(yPix * m_fZoom));
}
縮放因子 m_fZoom 由工具列按鈕/滑鼠滾輪調整,調整後呼叫 SetScrollSizes 更新捲軸範圍並 Invalidate()。
CToolPaletteBar (CToolBar 派生)TBBS_WRAPPED 樣式於第二欄第一個按鈕。CListCtrl (圖示檢視),切換工具時更新列表內容 (如選直線 → 顯示粗細樣式圖示)。CColorPaletteBar (CDialogBar 派生)OnPaint 手繪 16x16 色塊網格 (系統 20 色 + 自訂)。OnLButtonDown:左鍵設前景色,右鍵設背景色,雙擊彈出 CColorDialog 自訂。BOOL CMyView::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
if (nHitTest == HTCLIENT) // 僅客戶區改游標
{
switch (m_currentTool)
{
case TOOL_PEN: SetCursor(LoadCursor(NULL, IDC_PENCIL)); break;
case TOOL_LINE: SetCursor(LoadCursor(NULL, IDC_CROSS)); break;
case TOOL_TEXT: SetCursor(LoadCursor(NULL, IDC_IBEAM)); break;
// ...
}
return TRUE;
}
return CScrollView::OnSetCursor(pWnd, nHitTest, message);
}
// 位圖 → Base64 字串
CString BitmapToBase64(HBITMAP hBmp)
{
// GetDIBits 取得位元資料 → Base64 編碼 (CryptBinaryToString 或自實現)
}
// Base64 字串 → 位圖
HBITMAP Base64ToBitmap(const CString& b64)
{
// Base64 解碼 → CreateDIBitmap
}
| 功能 | 本程式 | Windows 畫圖板 | 備註 |
|---|---|---|---|
| 基本繪圖工具 | ✅ | ✅ | |
| 文字輸入 (字型/顏色可設) | ✅ | ✅ | 本程式支援任意字型 |
| 多級撤銷/重做 | ✅ | ❌ (僅 1 步) | 本程式優勢 |
| 縮放 (滑鼠滾輪/工具列) | ✅ | ✅ | |
| Base64 介面 | ✅ | ❌ | 資料庫/XML 整合用 |
| 圖塊拖放 | ✅ | ✅ |
本專案展示 MFC 文檔/視圖架構下完整繪圖應用的工程實踐:圖元物件導向、雙重緩衝、命令快照式撤銷、映射模式縮放、工具箱/調色盤自繪控制項、游標管理、Base64 序列化。代碼結構清晰,可直接作為專案起始模板或教學案例。
本文配套原始碼可供下載。