首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC++

《VC++技術內幕》學習筆記(七):GDI 核心 — CDC 設備環境、六大 GDI 物件、庫存物件、SelectObject 保存恢復模式

     編輯:VC++

Day 7 核心主題:圖形設備介面 (GDI) 核心架構

CDC:設備環境基類,GDI 的「遙控器」

  • 繼承自 CObject,封裝 Windows HDC (32 位句柄)。
  • **虛擬性**:同一繪圖代碼 (pDC->TextOut(...)) 可同時適用於螢幕 (CClientDC)、印表機 (CPrintDC)、列印預覽 (CPreviewDC)、記憶體位圖 (CMemDC) —— **設備無關性的核心體現**。
  • 常用派生類:
    CClientDC — 客戶區 DC (不含標題欄/邊框/菜單),原點 (0,0) 為客戶區左上角。
    CWindowDC — 整個窗口 DC (含非客戶區),原點 (0,0) 為螢幕左上角,可繪製邊框/標題欄。
    CPaintDC — BeginPaint/EndPaint 封裝,**只能在 OnPaint 內使用**。
    CMetaFileDC — 記錄元文件 (.emf)。

DC 對象生命週期管理:堆疊分配 = 自動清理

// 推薦:堆疊分配,函數返回自動調用析構函數釋放 HDC
void CMyView::OnLButtonDown(UINT nFlags, CPoint point) {
    CClientDC dc(this);           // 構造:GetDC(hWnd)
    CRect rect;
    dc.GetClipBox(&rect);         // 使用
} // 析構:ReleaseDC(hWnd, hDC) — 零洩漏

// 替代:手動管理 (易忘記 ReleaseDC 導致洩漏)
void CMyView::OnLButtonDown(UINT nFlags, CPoint point) {
    CDC* pDC = GetDC();           // 增加引用計數
    CRect rect;
    pDC->GetClipBox(&rect);
    ReleaseDC(pDC);               // 必須配對,否則 HDC 洩漏
}

六大 GDI 物件 (CGdiObject 派生類)

**注意**:CFont 和 CRgn **必須**先默認構造,再調用 CreateXxx;其他四類可直接用參數構造函數一次完成。

SelectObject:選入/選出 — 「保存舊、用新、還原舊」鐵律

void CMyView::OnDraw(CDC* pDC) {
    pDC->MoveTo(10,10); pDC->LineTo(110,10); // 預設筆 (黑、1像素、實線)

    CPen newPen(PS_DASHDOTDOT, 10, RGB(255,0,0)); // 紅、10像素、雙點劃線
    CPen* pOldPen = pDC->SelectObject(&newPen);   // 選入新筆,**返回舊筆指針**

    pDC->MoveTo(10,20); pDC->LineTo(110,20);     // 用新筆畫線

    pDC->SelectObject(pOldPen);                   // **還原舊筆** — 關鍵步驟!

    pDC->MoveTo(10,30); pDC->LineTo(110,30);     // 回到預設筆
}

**原則**:每次 SelectObject 選入新物件,**必須**在同一 DC 生命週期內選回舊物件,否則 GDI 物件洩漏、DC 狀態污染。

庫存物件:系統預建、永不刪除、直接用

調用 SelectStockObject 即可選入,無需構造、無需刪除、無需還原 (系統忽略刪除請求)。

類別功能典型構造方式Windows 句柄類型
CPen畫線、框架邊框 (顏色、寬度、樣式)CPen pen(PS_SOLID, 2, RGB(255,0,0));HPEN
CBrush填充區域 (實心、斜線、圖案、位圖)CBrush brush(RGB(0,255,0));HBRUSH
CFont字體 (字型、大小、粗細、斜體、字符集)CFont font; font.CreateFont(...); (兩階段構造)HFONT
CBitmap位圖 (像素矩陣、兼容位圖、DIB)CBitmap bmp; bmp.CreateCompatibleBitmap(pDC, w, h);HBITMAP
CPalette調色板 (256 色映射表、實現調色板動畫)CPalette pal; pal.CreatePalette(&lpLogPal);HPALETTE
CRgn區域 (矩形、橢圓、多邊形、組合)CRgn rgn; rgn.CreateEllipticRgn(...);HRGN

句柄持久化技巧:GetSafeHandle + FromHandle

問題:SelectObject 返回的 CGdiObject* 是**臨時指針**,閒置處理時會被框架刪除,不可直接存入成員變數。
解決:轉換為 Windows 句柄 (HGDIOBJ) 長期保存,需要時用 FromHandle 重建臨時 C++ 物件。

// 成員變數
HPEN m_hOldPen;

// 選入新筆並保存舊筆句柄
CPen newPen(PS_SOLID, 5, RGB(0,0,255));
CPen* pOldPen = pDC->SelectObject(&newPen);
m_hOldPen = (HPEN)pOldPen->GetSafeHandle(); // 保存句柄

// ... 繪圖 ...

// 還原:用句柄重建 C++ 物件指針
pDC->SelectObject(CPen::FromHandle(m_hOldPen));

**句柄類型對應表**:
CPen→HPEN, CBrush→HBRUSH, CFont→HFONT, CBitmap→HBITMAP, CPalette→HPALETTE, CRgn→HRGN。

GDI 物件析構鐵律

  • CGdiObject 有虛擬析構函數,派生類析構時會自動調用 DeleteObject 刪除對應的 Windows GDI 物件。
  • **堆疊分配的 GDI 物件** (如函數內部 CPen pen(...)) 會在作用域結束時自動刪除,**前提是已從所有 DC 中選出**。若仍被某 DC 選入,析構時會 ASSERT 失敗 (Debug) 或導致 DC 狀態異常。
  • **堆分配 (new)** 的 GDI 物件需手動 delete,同樣需先從 DC 選出。

雷神心法:GDI 物件管理三板斧

  1. **優先用庫存物件** — 免構造、免刪除、免還原、零洩漏風險。
  2. **自建物件必配對** — SelectObject(&newObj) 緊跟保存舊指針,繪圖結束前 SelectObject(pOldObj)。
  3. **跨函數/跨幀持久化** — 只存句柄 (GetSafeHandle),用時 FromHandle 重建臨時 C++ 包裹。
    (現代專案建議改用 std::shared_ptr 自定義 deleter 封裝 RAII)。

下一篇預告

Day 8:色彩與字體 — RGB 宏、COLORREF、SetTextColor/SetBkColor、CFont::CreateFont 21 參數詳解、TEXTMETRIC 字體度量、可縮放字體繪製實戰 (MM_ANISOTROPIC + Twips)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
類別常用庫存物件
BrushWHITE_BRUSH, LTGRAY_BRUSH, GRAY_BRUSH, DKGRAY_BRUSH, BLACK_BRUSH, NULL_BRUSH, HOLLOW_BRUSH
PenBLACK_PEN, WHITE_PEN, NULL_PEN
FontSYSTEM_FONT, DEFAULT_GUI_FONT, ANSI_VAR_FONT, ANSI_FIXED_FONT, OEM_FIXED_FONT, SYSTEM_FIXED_FONT, DEVICE_DEFAULT_FONT
PaletteDEFAULT_PALETTE (系統 20 靜態色)