TPrinter 封裝 Windows 打印子系統,提供設備無關畫布、分頁管理、多打印機選擇、打印作業生命週期控制的高層介面。全域單例 Printer()(定義於 Printers.hpp)即開箱即用。本文系統講解核心屬性、作業流程、畫布繪圖模式、常見陷阱與現代化列印架構建議。
TObject
└── TPrinter // 無繼承鏈,獨立封裝
// 全域訪問
#include <Printers.hpp>
TPrinter* Printer(); // 返回進程單例,內部引用計數管理
void PrintDocument() {
Printer()->BeginDoc(); // 1. 開啟作業,建立 DC
try {
// 繪製第 1 頁
DrawPage(1);
Printer()->NewPage(); // 2. 強制換頁,PageNumber++
// 繼續繪製...
DrawPage(2);
Printer()->EndDoc(); // 3. 正常結束,提交列印佇列
} catch (...) {
Printer()->Abort(); // 4. 異常中止,丟棄作業
throw;
}
}
關鍵點:
- BeginDoc 後才能訪問 Canvas。
- EndDoc 才真正送入佇列;Abort 丟棄未列印數據。
- NewPage 重置 Canvas->PenPos = (0,0),PageNumber++。
| 屬性 | 類型 | 說明 |
|---|---|---|
Canvas | TCanvas* | 當前頁畫布,支援所有繪圖方法 |
PageWidth/PageHeight | int | 當前頁像素寬高(依 DPI、方向變化) |
PageNumber | int | 當前頁碼(1 基),NewPage 遞增 |
Orientation | TPrinterOrientation | poPortrait/poLandscape,變更觸發 EndDoc |
Copies | int | 份數,驅動程式支援時生效 |
PrinterIndex | int | Printers 陣列索引,-1 為預設打印機 |
Printers | TStrings* | 系統安裝打印機名稱列表 |
Fonts | TStrings* | 當前打印機支援字體(含 TrueType 模擬) |
Title | AnsiString | 列印管理器顯示的作業名稱 |
Aborted | bool | 用戶/程式是否已中止 |
Printing | bool | 作業是否進行中 |
void DrawPage(int pageNo) {
TCanvas* c = Printer()->Canvas;
int pw = Printer()->PageWidth;
int ph = Printer()->PageHeight;
// 邏輯座標:建議以 0.1mm 為單位(或自定義)
const double MM = 0.1; // 1 单位 = 0.1mm
int dpiX = GetDeviceCaps(c->Handle, LOGPIXELSX);
int dpiY = GetDeviceCaps(c->Handle, LOGPIXELSY);
double scaleX = dpiX / 2540.0; // 2540 = 100 * 25.4 (0.1mm per inch)
double scaleY = dpiY / 2540.0;
c->Font->Size = 10;
c->Font->Name = "微軟正黑體";
c->TextOut(100 * scaleX, 50 * scaleY, "第 " + IntToStr(pageNo) + " 頁");
// 繪製表格線
c->Pen->Width = 1;
c->MoveTo(50 * scaleX, 100 * scaleY);
c->LineTo(500 * scaleX, 100 * scaleY);
}
關鍵:打印機 DC 採用裝置像素座標,無邏輯映射模式(MM_TEXT)。必須依 LOGPIXELSX/Y 自行換算物理單位。
// 列舉
for (int i = 0; i < Printer()->Printers->Count; ++i)
ListBox->Items->Add(Printer()->Printers->Strings[i]);
// 切換(會隱式 EndDoc 當前作業)
Printer()->PrinterIndex = desiredIndex; // 或 SetPrinter(name, driver, port)
// 獲取預設打印機資訊
char dev[256], drv[256], port[256];
int mode;
Printer()->GetPrinter(dev, drv, port, mode);
class ScopedPrintJob {
bool started;
public:
ScopedPrintJob(const AnsiString& title) : started(false) {
Printer()->Title = title;
Printer()->BeginDoc();
started = true;
}
~ScopedPrintJob() {
if (started) {
if (Printer()->Aborted) Printer()->Abort();
else Printer()->EndDoc();
}
}
TCanvas* Canvas() { return Printer()->Canvas; }
};
catch(...){ Printer()->Abort(); throw; } 確保不留殘留作業。EndDoc 死鎖,建議啟用 PrintToFile 測試或升級驅動。| 現象 | 原因 | 對策 |
|---|---|---|
| 輸出空白頁 | 未呼叫 BeginDoc 直接繪圖 | 嚴格遵循 BeginDoc → 繪圖 → EndDoc |
| 字體模糊/缺字 | 打印機不支援該字體,GDI 回退模擬 | 僅使用 Printer()->Fonts 列表中的字體 |
| 分頁位置錯誤 | 手動計算 Y 座標未考慮 PageHeight | 每次換頁檢查 PenPos.y > PageHeight - margin |
| 雙面列印失效 | TPrinter 無雙面屬性 | 需驅動程式 UI 設定或 DEVMODE 直接操作 |
對於複雜報表,建議棄用手繪 Canvas,改用:
① FastReport / ReportBuilder:VCL 原生報表引擎,支援數據綁定、分組、條碼、導出 PDF。
② HTML + wkhtmltopdf / Chromium Headless:以 Web 技術排版,轉 PDF 列印。
③ PDF 庫(PDFium, libharu):程式化生成 PDF,再 ShellExecute(print)。
TPrinter 仍是底層列印子系統的最終出口,掌握其畫布座標、作業生命週期與異常保護,是所有上層報表引擎穩定運作的基石。