VB 的 LoadPicture 一行搞定圖片顯示,VC++ 需手動操作 GDI 對象。本文以最經典的 SDI 應用精靈生成的框架 為例,手把手教你在 OnDraw 中繪製位圖,並解析每一步的 GDI 邏輯。
IDB_BITMAP1),**勿嘗試編輯位圖** (編輯器不支持 >256 色)。void CMyView::OnDraw(CDC* pDC) {
CMyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc) return;
// 1. 獲取客戶區矩形 (用於拉伸繪製)
CRect rect;
GetClientRect(&rect); // 注意:用 ClientRect 而非 WindowRect
// 2. 創建記憶體 DC (兼容螢幕 DC)
CDC memDC;
memDC.CreateCompatibleDC(pDC);
// 3. 載入位圖資源
CBitmap bmp;
bmp.LoadBitmap(IDB_BITMAP1); // 資源 ID
// 4. 選入位圖,保存舊對象
CBitmap* pOldBitmap = memDC.SelectObject(&bmp);
// 5. 位塊傳輸:將記憶體 DC 內容複製到螢幕 DC
// 參數:目標左上 (0,0)、寬高、源 DC、源左上 (0,0)、光柵操作
pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY);
// 6. 恢復記憶體 DC 狀態 (關鍵:防止 GDI 物件洩漏)
memDC.SelectObject(pOldBitmap);
// bmp、memDC 析構時自動釋放 GDI 物件
}
| 步驟 | API/成員 | 作用 | 類比 |
|---|---|---|---|
| 1 | GetClientRect | 獲取繪製目標區域大小 | 畫布尺寸 |
| 2 | CreateCompatibleDC | 創建與螢幕兼容的記憶體 DC | 離屏畫布 |
| 3 | CBitmap::LoadBitmap | 從資源加載位圖到 GDI 物件 | 載入圖片素材 |
| 4 | SelectObject | 將位圖關聯到記憶體 DC,返回舊位圖指針 | 裝上畫布 |
| 5 | BitBlt | 像素級複製 (SRCCOPY: 直接複製) | 蓋章印刷 |
| 6 | SelectObject(pOld) | 卸載位圖,恢復 DC 初始狀態 | 卸下畫布 |
若位圖尺寸與視圖不一致,需拉伸填滿:
// 獲取位圖原始尺寸
BITMAP bmInfo;
bmp.GetBitmap(&bmInfo);
// 拉伸繪製:目標區域 = 客戶區,源區域 = 位圖原始尺寸
pDC->StretchBlt(0, 0, rect.Width(), rect.Height(),
&memDC, 0, 0, bmInfo.bmWidth, bmInfo.bmHeight, SRCCOPY);
缺點:StretchBlt 最近鄰插值,縮放質量差 (鋸齒、馬賽克)。高質量縮放請參考 SetStretchBltMode(HALFTONE) 或 GDI+ / DrawDib 方案。
resource.h 確認 IDB_BITMAP1 定義存在。SetStretchBltMode(pDC->m_hDC, HALFTONE) + SetBrushOrgEx 或改用 DrawDib。SelectObject(pOld),否則位圖無法刪除,導致 GDI 計數耗盡程式崩潰。BitBlt 前後設置斷點,觀察視窗客戶區像素變化;或使用 GdiSetBatchLimit(1) 強制即時刷新。OnHScroll/OnVScroll 修改源偏移量傳給 BitBlt。TransparentBlt (需 Msimg32.lib) 或手動掩膜。OnEraseBkgnd 返回 TRUE,所有繪製在記憶體 DC 完成後一次 BitBlt 到螢幕。CImage (ATL/MFC 共享類) 支持 JPEG/PNG/GIF 直接加載:img.Load("pic.jpg"); img.Draw(pDC->m_hDC, rect);「記憶體 DC + SelectObject + BitBlt」是 Windows GDI 繪圖的「Hello World」核心模式。掌握此流程,即掌握了所有位圖操作、離屏繪製、打印預覽、截圖工具的基因。建議新手反覆練習,直到能閉眼寫出正確的六步驟代碼,並理解每一步的資源所有權轉移邏輯。