TCanvas 是 VCL 繪圖子系統的抽象畫布,封裝 Windows GDI HDC,提供筆刷字體狀態管理、幾何圖元繪製、文本輸出、位圖操作、剪裁區域等完整能力。所有可視控件(TForm、TPaintBox、TBitmap、TMetafile、TPrinter)均通過 Canvas 屬性暴露繪圖介面。本文系統梳理核心屬性、繪圖方法分類、座標系統、狀態鎖定、事件模型與高性能渲染模式。
TObject
└── TPersistent
└── TCanvas
├── TControlCanvas // 控件畫布(自動綁定控件 HDC)
└── TMetafileCanvas // 圖元檔錄製畫布
// 核心狀態屬性(均為物件包裝,賦值即 Assign,非替換指標)
__property TBrush* Brush; // 填充:顏色、風格、位圖
__property TPen* Pen; // 線條:顏色、寬度、風味、模式
__property TFont* Font; // 字體:名稱、大小、風格、字符集、顏色
__property TRect ClipRect; // 唯讀,當前剪裁矩形
__property int CopyMode; // 位圖複製模式(ROP2 代碼)
__property TPoint PenPos; // 當前筆位置(LineTo 終點)
| 類別 | 方法 | 說明 |
|---|---|---|
| 幾何圖元 | MoveTo/LineTo | 移動/畫線到指定點 |
Polyline/Polygon | 多段線/閉合多邊形(自動填充) | |
Rectangle/RoundRect | 矩形/圓角矩形(筆畫框+刷填充) | |
Ellipse/Chord/Pie | 橢圓/弓形/扇形(筆畫框+刷填充) | |
Arc | 橢圓弧(需起止向量) | |
PolyBezier/PolyBezierTo | 貝茲曲線組(4 點一段) | |
FrameRect | 僅畫矩形框(1 像素寬,用 Brush) | |
FillRect/FloodFill | 矩形填充/區域漫水填充 | |
| 文本輸出 | TextOut | 指定點輸出字串,更新 PenPos |
TextRect | 剪裁矩形內輸出 | |
TextWidth/TextHeight/TextExtent | 測量字串像素尺寸 | |
| 位圖/圖像 | Draw/StretchDraw | 原尺寸/縮放繪製 TGraphic |
CopyRect | 從另一 Canvas 複製矩形區 | |
BrushCopy | 位圖透明色替換複製 | |
| 狀態控制 | Lock/Unlock/TryLock | 多執行緒繪圖同步(LockCount 計數) |
Refresh | 重置 HDC 筆刷字體選擇 |
OnPaint 時系統自動設為需重繪區域(InvalidateRect 累積)。Canvas->ClipRect = Rect(10,10,200,200); 後續繪圖超出部分被剪裁。// 錯誤:以為替換物件,實為 Assign
Canvas->Brush = otherBrush; // 實際執行 Canvas->Brush->Assign(otherBrush)
// 正確:直接操作屬性
Canvas->Brush->Color = clRed;
Canvas->Brush->Style = bsSolid;
Canvas->Pen->Width = 2;
Canvas->Font->Name = "Consolas";
原則:Brush/Pen/Font 屬性為包裝物件,生命週期由 Canvas 管理,切勿 delete 或 new 替換。
// 背景執行緒繪製位圖,再主執行緒 Blit 到螢幕
TBitmap* bmp = new TBitmap();
bmp->Width = 800; bmp->Height = 600;
// 背景執行緒
bmp->Canvas->Lock(); // 防止主執行緒同時訪問
try {
// 大量繪圖操作...
} __finally {
bmp->Canvas->Unlock();
}
// 主執行緒 Paint 事件
Form->Canvas->Draw(0, 0, bmp);
注意:TControlCanvas 擁有自有 HDC,不可 手動 Lock/Unlock,VCL 內部已處理。
| 事件 | 觸發時機 | 用途 |
|---|---|---|
OnChanging | 繪圖方法調用前 | 預處理、記錄 Dirty 區域 |
OnChange | 繪圖方法調用後 | 觸發重繪通知、更新捲軸範圍 |
屬性變更(Pen->Color = ...)不觸發 Canvas 事件,僅觸發包裝物件自身事件。
TBitmap 離屏繪製 → Draw(0,0,bmp) 一次 Blit,消除閃爍。PolyLine/Polygon/PolyBezier 單次調用替代循環 LineTo,減少 GDI 呼叫開銷。TBitmap->ScanLine[y] 直接操作記憶體。SelectObject 開銷大,預建 TFont 物件池複用。TMetafile,重複播放極快(GDI 播放優化)。| 現象 | 原因 | 對策 |
|---|---|---|
| 繪圖不顯示 | 在 OnPaint 外繪製控件 Canvas | 僅在 OnPaint 或 TPaintBox::OnPaint 中繪製 |
| 線條寬度異常 | Pen->Width > 1 時風格必須 psSolid | 虛線/點線強制 1 像素寬 |
| 文字底色不透明 | Brush->Style != bsClear | 文本輸出前設 Canvas->Brush->Style = bsClear |
| 高 DPI 模糊 | 未啟用 Per-Monitor DPI Aware | Manifest + Application->Scaled = true |
TCanvas 以極簡 API 隱藏了 GDI 的繁瑣細節,掌握其狀態管理語義、座標系統、剪裁機制與批次繪圖模式,即可在 VCL 應用中實現流暢、高性能的自定義繪圖。