Эта страница может содержать автоматически переведенный текст.

Контейнер Table

Используйте контейнеры Table для размещения самых сложных данных. Таблицы играют решающую роль в документах PDF, повышая их ясность и эффективность.

Таблицы предоставляют структурированный формат представления данных. Они позволяют представить сложные детали в краткой и визуально привлекательной форме. Вместо длинных абзацев вы можете использовать таблицы, чтобы кратко представить факты, цифры и тенденции.

Хорошо продуманная таблица повышает читабельность. Выравнивая столбцы и строки, вы создаете структуру, которая направляет взгляд читателя. Вы можете использовать фон, границы и стили шрифта, чтобы выделить определенные ячейки или заголовки.

Контейнер Table

Получите контейнер таблицы, вызвав метод LayoutContainer.Table. Затем вы должны определить хотя бы один столбец в контейнере. В простейшем случае вы можете заполнить все столбцы и строки, вызвав метод Cell несколько раз.

Продолжайте читать, чтобы узнать, как определять столбцы, добавлять верхний и/или нижний колонтитул в таблицу. В тексте также описаны все остальные возможности контейнера Table.

Эта статья - часть серии статей про Layout API для генерации PDF-файлов. Если вы новичок в работе с API, то сначала прочитайте часть Начало работы с Layout API.

Библиотека Docotic.Pdf 9.4.17467-dev Дополнение Layout 9.4.17467-dev
Регрессионные тесты 14,760 прошло Всего загрузок NuGet 4,415,970

Столбцы и строки

Каждая таблица должна определять хотя бы один столбец. Используйте метод Table.Columns для определения столбцов. Метод принимает делегат типа Action<TableColumnContainer>. В коде делегата добавьте столбцы в таблицу, вызвав методы предоставленного объекта.

Метод ConstantColumn добавляет столбец c шириной, равной точному количеству точек.

Метод RelativeColumn добавляет столбец с относительной шириной. Метод принимает количество частей, которые должен занимать столбец. Общее количество частей — это сумма всех чисел во всех вызовах RelativeColumn в этой таблице. В основном этот метод работает так же, как и RelativeItem контейнера Row. Пожалуйста, прочитайте ссылку для подробного объяснения.

Нет необходимости явно определять строки. Количество строк зависит от свойств столбцов и ячеек. Количество ячеек также может влиять на количество строк. Таблица может иметь высоту, превышающую сумму высот всех ее строк. Строки могут иметь меньше ячеек, чем количество столбцов. И столбцы могут иметь меньше ячеек, чем количество строк.

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>. В коде делегата укажите строку и индекс ячейки, вызвав методы RowIndex и ColumnIndex предоставленного объекта.

Для новой ячейки можно указать только индекс строки или столбца. Индекс столбца равен 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.

Таблицы могут иметь верхний и нижний колонтитулы. Используйте методы Header и Footer для доступа и настройки соответствующего 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.