該頁面可以包含自動翻譯的文字。

表格容器

使用 表格 容器來排版你最複雜的資料。表格在 PDF 文件中扮演關鍵角色,可提升其清晰度與效果。

表格提供結構化格式來呈現資料。它讓你能以簡潔且具視覺吸引力的方式呈現複雜細節。你可以用表格取代冗長段落,以簡明方式呈現事實、數字與趨勢。

設計良好的表格可提升可讀性。透過對齊欄與列,你建立出引導讀者視線的結構。你可以使用背景、邊框與字型樣式來強調特定儲存格或標題。

表格容器

透過呼叫 LayoutContainer.Table 方法取得表格容器。接著你必須在容器中定義至少一個欄。在最簡單的情況下,你可以多次呼叫 Cell 方法來填滿所有欄與列。

請繼續閱讀,了解如何定義欄,以及如何為表格新增標頭和/或頁尾。本文也會說明 Table 容器的其他所有功能。

本文是 Layout API 用於 PDF 產生系列的一部分。如果你是第一次接觸此 API,請先閱讀 Layout API 入門 部分。

欄與列

每個表格都必須至少定義一個欄。使用 Table.Columns 方法定義欄。此方法接受型別為 Action<TableColumnContainer> 的委派。在委派程式碼中,透過呼叫所提供物件的方法將欄加入表格。

ConstantColumn 方法會加入一個寬度等於精確點數的欄。

RelativeColumn 方法會加入一個相對寬度的欄。此方法接受欄應佔用的部分數。總部分數是此表格中所有 RelativeColumn 呼叫的數字總和。大多數情況下,此方法的運作方式與 Row 容器RelativeItem 相同。請閱讀連結以了解詳細說明。

不需要明確定義列。列數取決於欄與儲存格屬性。儲存格數量也會影響列數。表格的高度可以大於其所有列高度總和。列可以包含少於欄數的儲存格。欄也可能包含少於列數的儲存格。

PdfDocumentBuilder.Create().Generate("table-basic.pdf", doc =>
{
    doc.Pages(page =>
    {
        page.Size(300, 100);
        page.Content()
            .Padding(10)

            //.MinimalBox()

            .Border(b => b.Thickness(1).Color(new PdfRgbColor(250, 123, 5)))
            .Table(table =>
            {
                table.Columns(columns =>
                {
                    columns.ConstantColumn(100);
                    columns.RelativeColumn(1);
                    columns.RelativeColumn(3);
                });

                for (int i = 0; i < 10; i++)
                {
                    table.Cell()
                        .Border(b => b.Thickness(0.1))
                        .AlignCenter()
                        .Text($"Cell {i + 1}");
                }
            });
    });
});

你可以在 table-basic.pdf 看到程式碼的結果。

在產生的 PDF 中,表格佔滿所有父容器空間,即使沒有足夠的儲存格覆蓋整個區域。取消註解 MinimalBox 呼叫,可將表格高度限制為其各列高度總和。

這段程式碼定義了三個欄,並向表格新增 10 個儲存格。表格會依序放置儲存格,並在需要時新增一列。只有一個儲存格的列完全沒有問題。

如果目前頁面空間不足,表格會將儲存格放到下一頁。你可以透過增加新增的儲存格數量來觀察這點。

儲存格

以下說明 Layout API 在處理表格儲存格時提供的功能。

位置

建立表格儲存格有兩種方式:

  • 不明確指定位置資訊
  • 至少部分指定位置

我在前一節的程式碼中使用了第一種方式。你呼叫不接受參數的 Cell 方法多載。此方法會在最後一個新增儲存格之後的欄中加入新儲存格。如果右側沒有空間,儲存格會移到下一列。在 RTL 模式下,方法會檢查最後一個新增儲存格左側是否有空間。

如果你知道新儲存格的確切位置,請使用 Cell 方法的另一個多載。也就是接受型別為 Action<TableCell> 的委派的那個。在委派程式碼中,透過呼叫所提供物件的 RowIndexColumnIndex 方法指定儲存格列與索引。

新儲存格可以只指定列索引或欄索引。只指定列索引時,欄索引為 0。反之亦然。

建立儲存格時,可以任意順序混用兩種 Cell 多載的呼叫。

在本文後面的程式碼範例中,我會使用下列 擴充方法。這樣可以讓範例更簡潔,也更容易閱讀。

static class LayoutHelpers
{
    public static TextSpan BoxWithText(this LayoutContainer cell, string caption)
        => cell.Border(b => b.Thickness(0.5))
            .Background(new PdfGrayColor(75))
            .ShowOnce()
            .MinWidth(50)
            .MinHeight(50)
            .Padding(10)
            .AlignCenter()
            .AlignMiddle()
            .Text(caption);
}

以下範例建立了一個同時包含明確與隱式放置儲存格的表格。我使用擴充方法為儲存格套用樣式與文字。儲存格中的數字反映儲存格建立順序。

PdfDocumentBuilder.Create().Generate("table-cells.pdf", doc =>
{
    doc.Pages(page =>
    {
        page.Size(300, 200);
        page.Content()
            .Padding(10)
            .MinimalBox()
            .Border(b => b.Thickness(0.1))
            .Table(table =>
            {
                table.Columns(columns =>
                {
                    for (int i = 0; i < 4; i++)
                        columns.RelativeColumn();
                });

                table.Cell().BoxWithText("1");
                table.Cell(c => c.RowIndex(1).ColumnIndex(1)).BoxWithText("2");
                table.Cell().BoxWithText("3");
                table.Cell(c => c.RowIndex(2).ColumnIndex(2)).BoxWithText("4");
                table.Cell(c => c.RowIndex(0).ColumnIndex(3)).BoxWithText("5");
            });
    });
});

你可以在 table-cells.pdf 看到程式碼的結果。

列與欄跨距

儲存格可以跨越多列和/或多欄。使用接受委派的 Cell 多載來指定儲存格跨越的列數與欄數。

PdfDocumentBuilder.Create().Generate("table-cellspans.pdf", doc =>
{
    doc.Pages(page =>
    {
        page.Size(300, 150);
        page.Content()
            .Padding(10)
            .MinimalBox()
            .Border(b => b.Thickness(0.1))
            .Table(table =>
            {
                table.Columns(columns =>
                {
                    for (int i = 0; i < 4; i++)
                        columns.RelativeColumn();
                });

                table.Cell(c => c.RowSpan(2).ColumnSpan(2)).BoxWithText("1");
                table.Cell(c => c.ColumnSpan(2)).BoxWithText("2");
                table.Cell().BoxWithText("3");
                table.Cell().BoxWithText("4");
            });
    });
});

你可以在 table-cellspans.pdf 看到程式碼的結果。

重疊

儲存格有可能互相覆蓋。發生這種情況時,建立順序會決定哪個儲存格位於最上層。

當儲存格跨越多欄或多列時,可能會出現部分重疊。或者當你同時以明確與隱式方式定位儲存格時,也可能發生。

PdfDocumentBuilder.Create().Generate("table-overlapping.pdf", doc =>
{
    doc.Pages(page =>
    {
        page.Size(300, 300);
        page.Content()
            .Padding(10)
            .MinimalBox()
            .Border(b => b.Thickness(0.1))
            .Table(table =>
            {
                table.Columns(columns =>
                {
                    for (int i = 0; i < 4; i++)
                        columns.RelativeColumn();
                });

                for (int i = 0; i < 16; i++)
                    table.Cell().BoxWithText($"{i + 1}");

                table.Cell(c => c.RowIndex(2).ColumnIndex(1).ColumnSpan(3))
                    .Background(new PdfRgbColor(250, 123, 5), 50);
            });
    });
});

你可以在 table-overlapping.pdf 看到程式碼的結果。

延伸到底部

如果表格的版面配置較複雜,分頁機制可能會使某些欄底部出現空白區域。如果你想消除空白區域,請使用 ExtendCellsToBottom 方法。此方法會延伸每個欄中的最後一個儲存格,使儲存格在表格底部結束。

在下列範例中,第一頁顯示預設行為。第二頁顯示 ExtendCellsToBottom 呼叫如何影響各欄。

PdfDocumentBuilder.Create().Generate("table-extendcells.pdf", doc =>
{
    for (int take = 0; take < 2; take++)
    {
        doc.Pages(page =>
        {
            page.Size(300, 200);
            page.Content()
                .Padding(10)
                .MinimalBox()
                .Border(b => b.Thickness(0.1))
                .Table(table =>
                {
                    if (take != 0)
                        table.ExtendCellsToBottom();

                    table.Columns(columns =>
                    {
                        for (int i = 0; i < 4; i++)
                            columns.RelativeColumn();
                    });

                    table.Cell(c => c.RowIndex(0).ColumnIndex(0)).BoxWithText("1");
                    table.Cell(c => c.RowIndex(2).ColumnIndex(0)).BoxWithText("2");
                    table.Cell(c => c.RowIndex(1).ColumnIndex(1)).BoxWithText("3");
                    table.Cell(c => c.RowIndex(2).ColumnIndex(2)).BoxWithText("4");
                    table.Cell(c => c.RowIndex(1).ColumnIndex(3)).BoxWithText("5");
                });
        });
    }
});

你可以在 table-extendcells.pdf 看到程式碼的結果。

表格可以有標頭與頁尾。使用 HeaderFooter 方法存取並設定對應的 TableCellContainer。容器提供 Cell 方法來建立儲存格。這些方法的運作方式與主表格內容的方法完全相同。

標頭、頁尾與主表格儲存格使用相同的欄定義。但三組儲存格集合彼此獨立,互不影響。

當表格無法放入單一頁面時,標頭與頁尾會在表格佔用的每一頁上重複出現。

我在前面的範例中使用了 BoxWithText 擴充方法。接下來的程式碼中也會使用 WhiteBoxWithText 擴充方法。

static class LayoutHelpers
{
    public static TextSpan WhiteBoxWithText(
        this LayoutContainer cell, string caption, bool center = true)
    {
        return cell.Border(b => b.Thickness(0.5))
            .ShowOnce()
            .MinWidth(50)
            .MinHeight(50)
            .Padding(10)
            .Container(l => center ? l.AlignCenter() : l)
            .AlignMiddle()
            .Text(caption);
    }
}

以下範例示範如何建立帶有標頭的表格。我使用一個包含五種重要化學元素的表格。每個元素都有名稱,以及攝氏與開爾文的熔點。

var elements = new (string Name, double Celsius, double Kelvin)[] {
    ("Oxygen", -218.79, 54.36),
    ("Carbon", double.NaN, double.NaN),
    ("Hydrogen", -259.16, 13.99),
    ("Nitrogen", -209.86, 63.23),
    ("Sulfur", 115.21, 388.36),
};

static string formatDouble(double val)
{
    return double.IsNaN(val)
        ? string.Empty
        : val.ToString(CultureInfo.InvariantCulture);
}

PdfDocumentBuilder.Create().Generate("table-header.pdf", doc =>
{
    doc.Pages(page =>
    {
        page.Size(400, 500);
        page.Content()
            .Padding(10)
            .MinimalBox()
            .Border(b => b.Thickness(0.1))
            .Table(table =>
            {
                table.Columns(columns =>
                {
                    columns.RelativeColumn();
                    columns.ConstantColumn(100);
                    columns.ConstantColumn(100);
                });

                table.Header(header =>
                {
                    header.Cell(c => c.RowSpan(2)).BoxWithText("Chemical element");
                    header.Cell(c => c.ColumnSpan(2)).BoxWithText("Melting point");

                    header.Cell().BoxWithText("Celsius");
                    header.Cell().BoxWithText("Kelvin");
                });

                foreach (var (Name, Celsius, Kelvin) in elements)
                {
                    table.Cell().WhiteBoxWithText(Name, false);

                    table.Cell().WhiteBoxWithText(formatDouble(Celsius));
                    table.Cell().WhiteBoxWithText(formatDouble(Kelvin));
                }
            });
    });
});

你可以在 table-header.pdf 看到程式碼的結果。

範例程式碼

請花一些時間查看 將表格新增至 PDF 文件 範例。它也示範如何建立帶有標頭的表格,但不使用任何擴充方法。而且也有 VB.NET 版本。