Skip to main content

元件與版面

QuestPDF 的版面系統以 容器元件 為核心。容器負責排列方向和空間分配,元件負責呈現內容。


Component 抽象繼承關係

建立可重用的報表元件時,QuestPDF 提供 3 個關鍵 interface:

  • IContainer — slot 型別,任何能被「寫入內容」的位置都是它(page.Content() 回傳的就是 IContainer)。
  • IComponent靜態 可重用元件;覆寫 Compose(IContainer) 把內容畫入給定 slot。
  • IDynamicComponent<TState>動態、可分頁元件;覆寫 Compose(DynamicContext) 並保有跨頁狀態。

下圖是三者與應用層元件的關係:

使用時機對照:

抽象何時選用典型範例
IContainer你是「被寫入方」(slot);幾乎所有 Fluent API 參數page.Content(), col.Item(), row.RelativeItem() 的回傳值
IComponent要把一段版面邏輯封裝成可重用單元、且不需要感知分頁狀態報表頁首、公司 logo+標題區塊、簽名欄
IDynamicComponent<TState>內容需要跨頁保持狀態,例如「第 N 頁接續上一頁的資料列」長資料表的續行、跨頁註腳、已用空間計算

使用 IComponent 的呼叫端:

// 呼叫端
page.Header().Component(new TableHeaderComponent {
ReportTitle = "設備參數報表",
GeneratedAt = DateTime.Now
});

// 元件端
public class TableHeaderComponent : IComponent {
public string ReportTitle { get; init; } = "";
public DateTime GeneratedAt { get; init; }

public void Compose(IContainer container) {
container.Column(col => {
col.Item().Text(ReportTitle).FontSize(20).Bold();
col.Item().Text($"產出:{GeneratedAt:yyyy/MM/dd HH:mm}");
});
}
}

讀圖重點:

  • IComponentIContainer 是兩個方向:前者是「要畫的東西」,後者是「要畫進去的位置」;Compose(IContainer) 就是把兩者接起來。
  • IDynamicComponent<TState>TState 不是任意型別:常見用 int(當前頁索引)或自訂 struct;QuestPDF 在每次 Compose 後把回傳的 DynamicComponentComposeResult.State 存下來,下一頁 Compose 時回灌進 State 屬性。
  • 不要在 IComponent 裡存可變狀態:會破壞 QuestPDF 對「每次 Generate 都從零開始」的假設,造成跨份報表互相污染。有狀態需求一律用 IDynamicComponent<TState>

版面容器

Column — 垂直排列

page.Content().Column(col =>
{
col.Spacing(8); // 每個項目間距 8pt

col.Item().Text("參數 1:溫度");
col.Item().Text("參數 2:壓力");
col.Item().Text("參數 3:轉速");
});

Row — 水平排列

page.Content().Row(row =>
{
// 固定寬度
row.ConstantItem(100).Text("設備名稱:");

// 佔滿剩餘空間
row.RelativeItem().Text("PLC-001");

// 比例分配
row.RelativeItem(2).Text("佔 2/3 寬度");
row.RelativeItem(1).Text("佔 1/3 寬度");
});

巢狀組合

page.Content().Column(col =>
{
col.Item().Row(row =>
{
row.RelativeItem().Column(innerCol =>
{
innerCol.Item().Text("設備:PLC-001");
innerCol.Item().Text("型號:Mitsubishi FX5U");
});

row.ConstantItem(80).Image("device-photo.jpg");
});
});

Table — 表格

基本表格

page.Content().Table(table =>
{
// 定義欄寬
table.ColumnsDefinition(columns =>
{
columns.RelativeColumn(3); // 參數名稱(佔 3 份)
columns.RelativeColumn(2); // 數值(佔 2 份)
columns.RelativeColumn(1); // 單位(佔 1 份)
columns.ConstantColumn(80); // 狀態(固定 80pt)
});

// 表頭
table.Header(header =>
{
header.Cell().Background(Colors.Grey.Darken2)
.Padding(5).Text("參數名稱").FontColor(Colors.White).Bold();
header.Cell().Background(Colors.Grey.Darken2)
.Padding(5).Text("數值").FontColor(Colors.White).Bold();
header.Cell().Background(Colors.Grey.Darken2)
.Padding(5).Text("單位").FontColor(Colors.White).Bold();
header.Cell().Background(Colors.Grey.Darken2)
.Padding(5).Text("狀態").FontColor(Colors.White).Bold();
});

// 資料列
foreach (var param in parameters)
{
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(5).Text(param.Name);
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(5).AlignRight().Text(param.Value.ToString("F2"));
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(5).Text(param.Unit);
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(5).Text(param.IsNormal ? "正常" : "異常")
.FontColor(param.IsNormal ? Colors.Green.Darken1 : Colors.Red.Darken1);
}
});

合併儲存格

table.Cell().RowSpan(3).Text("區域 A");     // 垂直合併 3 列
table.Cell().ColumnSpan(2).Text("合併欄位"); // 水平合併 2 欄

Text — 文字

基本文字

container.Text("純文字");
container.Text("粗體").Bold();
container.Text("斜體").Italic();
container.Text("大字").FontSize(20);
container.Text("彩色").FontColor(Colors.Red.Medium);
container.Text("底線").Underline();

混合樣式(Rich Text)

container.Text(text =>
{
text.Span("設備 ");
text.Span("PLC-001").Bold().FontColor(Colors.Blue.Medium);
text.Span(" 溫度超標:");
text.Span("92.5°C").Bold().FontColor(Colors.Red.Medium);
text.Span("(上限 80°C)");
});

DefaultTextStyle

在頁面或容器層級設定預設文字樣式:

page.DefaultTextStyle(style =>
style.FontFamily("Microsoft JhengHei")
.FontSize(10)
.FontColor(Colors.Grey.Darken3));

Image — 圖片

// 從檔案
container.Image("company-logo.png");

// 從 byte[]
container.Image(imageBytes);

// 從 Stream
container.Image(stream);

// 控制大小
container.Width(200).Image("device-photo.jpg");

// 填滿可用空間
container.Image("photo.jpg").FitArea();

Page 設定

頁面大小與邊距

page.Size(PageSizes.A4);           // A4 直式
page.Size(PageSizes.A4.Landscape()); // A4 橫式
page.Size(new PageSize(100, 50, Unit.Millimetre)); // 自訂(標籤尺寸)

page.MarginTop(2, Unit.Centimetre);
page.MarginBottom(2, Unit.Centimetre);
page.MarginHorizontal(1.5f, Unit.Centimetre);
page.Header().Row(row =>
{
row.RelativeItem().Column(col =>
{
col.Item().Text("GatherTech 設備參數報表")
.FontSize(14).Bold();
col.Item().Text($"產出日期:{DateTime.Now:yyyy/MM/dd HH:mm}")
.FontSize(8).FontColor(Colors.Grey.Medium);
});

row.ConstantItem(60)
.AlignRight()
.Image("company-logo.png");
});

page.Footer().AlignCenter().Text(text =>
{
text.DefaultTextStyle(s => s.FontSize(8).FontColor(Colors.Grey.Medium));
text.Span("第 ");
text.CurrentPageNumber();
text.Span(" / ");
text.TotalPages();
text.Span(" 頁");
});

進階元件

ShowOnce — 只在第一頁顯示

page.Content().Column(col =>
{
// 封面資訊只在第一頁
col.Item().ShowOnce().Column(inner =>
{
inner.Item().Text("設備驗證報告").FontSize(24).Bold();
inner.Item().Text($"設備:{deviceName}");
inner.Item().Text($"日期:{DateTime.Now:yyyy/MM/dd}");
inner.Item().PaddingBottom(20);
});

// 資料表格會自動分頁
col.Item().Table(table => { /* ... */ });
});

Decoration — 重複的頁首區域

page.Content().Decoration(decoration =>
{
// Before:每頁頂部都會出現
decoration.Before().BorderBottom(1).BorderColor(Colors.Grey.Medium)
.PaddingBottom(5).Text("設備參數清單").Bold();

// Content:會自動分頁
decoration.Content().Table(table => { /* ... */ });
});

PageBreak — 手動分頁

col.Item().PageBreak();

背景色與框線

container
.Background(Colors.Grey.Lighten4)
.Border(1)
.BorderColor(Colors.Grey.Medium)
.Padding(10)
.Text("有框線有背景的區塊");

水印

page.Foreground().Text("機密文件")
.FontSize(60)
.FontColor(Colors.Red.Lighten3)
.Bold();

QuestPDF Previewer

開發時可用 QuestPDF Previewer 即時預覽,不需要每次產出 PDF 檔案:

dotnet add package QuestPDF.Previewer
// 開發環境使用 Previewer
document.ShowInPreviewer(); // 會開啟預覽視窗

Previewer 支援即時更新——修改程式碼、重新執行,預覽視窗自動刷新。


下一步報表 Pattern — 完整的報表模板與批次產出