Разрабатываем печать документов на .NET с помощью OpenXml. Часть 2

Всем привет! Я Александр Родов, ведущий разработчик в компании «БАРС Груп», автор и руководитель разработки сервиса генерации печатных форм Sprinter. Этой статьей мы продолжаем рассказ о возможностях использования библиотек DocumentFormat.OpenXml для генерации печатных файлов «офисных» форматов.

Напомним, в предыдущей части мы сформулировали постановку нашей демонстрационной задачи, а именно разработать печать данных заказа в некотором абстрактном интернет-магазине. Далее реализовали первую часть документа, содержащую шапку документа и колонтитул с логотипом магазина. Подробнее с постановкой задачи и примерами исходного кода можно ознакомиться по ссылке выше. Ну а на очереди у нас — печать таблиц в docx!

Разрабатываем печать документов на .NET с помощью OpenXml. Часть 2 - 1

Создание и настройка таблицы

Для создания пустой таблицы в docx заполним объекты с настройками таблицы, которые будут являться дочерними для объекта таблицы в структуре OpenXml. Объект TableProperties содержит описание границ и макета таблицы, а объект TableGrid — описание набора столбцов таблицы. Для границ зададим цвет, размер и стиль — одинарную границу. Для столбцов — в нашей таблице их будет шесть — укажем ширину в единицах TWIP, описание которых приводилось в предыдущей статье.

var tableProps = new TableProperties
{

    TableBorders = new TableBorders
    {
        LeftBorder = new()
        {
            Color = DefaultColor,
            Size = 12u,
            Val = BorderValues.Single
        },
        RightBorder = new()
        {
            Color = DefaultColor,
            Size = 12u,
            Val = BorderValues.Single
        },
        TopBorder = new()
        {
            Color = DefaultColor,
            Size = 12u,
            Val = BorderValues.Single
        },
        BottomBorder = new()
        {
            Color = DefaultColor,
            Size = 12u,
            Val = BorderValues.Single
        },
    },
    TableLayout = new TableLayout { Type = TableLayoutValues.Fixed }
};

var tableGrid = new TableGrid(
    new GridColumn { Width = CmToTwip(1).ToString() },
    new GridColumn { Width = CmToTwip(3).ToString() },
    new GridColumn { Width = CmToTwip(4.5f).ToString() },
    new GridColumn { Width = CmToTwip(2.5f).ToString() },
    new GridColumn { Width = CmToTwip(3).ToString() },
    new GridColumn { Width = CmToTwip(3).ToString() });

var wordTable = new Table(tableProps, tableGrid); 

document.MainDocumentPart.Document.Body.Append(wordTable);

Далее заполним титульную строку таблицу. Для этого инициализируем  объект TableRow и добавим в него шесть объектов TableCell с указанием текста заголовка и настроек границ ячейки. К самому тексту в ячейке применим настройки стилей текста.

var cellBorders = new TableCellBorders
{
    LeftBorder = new()
    {
        Color = DefaultColor,
        Size = 12u,
        Val = BorderValues.Single
    },
    RightBorder = new()
    {
        Color = DefaultColor,
        Size = 12u,
        Val = BorderValues.Single
    },
    TopBorder = new()
    {
        Color = DefaultColor,
        Size = 12u,
        Val = BorderValues.Single
    },
    BottomBorder = new()
    {
        Color = DefaultColor,
        Size = 12u,
        Val = BorderValues.Single
    },
};


var titleRow = new TableRow(new TableRowProperties(new TableHeader()));
var tableTitles = new[] { "№", "Код", "Наименование", "Количество", "Цена", "Стоимость" };
foreach (var title in tableTitles)
{
    var cell = new TableCell
    {
        TableCellProperties = new TableCellProperties
        {
            Shading = new Shading { Fill = "DDDDDD", Val = ShadingPatternValues.Clear },
            TableCellBorders = (TableCellBorders)cellBorders.CloneNode(true)
        }
    };
    
    SetCellText(cell, title, JustificationValues.Center);
    titleRow.Append(cell);
}

wordTable.Append(titleRow);


void SetCellText(TableCell cell, string text, JustificationValues justificationValues, bool? bold = null)
{
    var paragraphProperties = new ParagraphProperties
    {
        ParagraphStyleId = new ParagraphStyleId { Val = TableStyleId },
        Justification = new Justification { Val = justificationValues },
        SpacingBetweenLines = new SpacingBetweenLines
        {
            After = "0",
            Line = "360",
            LineRule = LineSpacingRuleValues.Auto
        }
    };

    cell.Append(new Paragraph(new Run(new Text(text) { Space = SpaceProcessingModeValues.Preserve })
        {
            RunProperties = bold.HasValue ? new RunProperties { Bold = new Bold { Val = bold.Value } } : null
        })
        { ParagraphProperties = paragraphProperties });
}

Заполнение ячеек

Теперь нам необходимо внести данные заказа в таблицу. Алгоритм вставки ячеек в таблицу здесь будет аналогичен тому, как это делалось для строки шапки таблицы. Разница будет лишь в параметрах стилей (жирное начертание, горизонтальное выравнивание), которые передаются в метод SetCellText. 

Ещё раз стоит обратить внимание на то, что для переиспользования одних и тех же объектов OpenXml в документе их необходимо клонировать, таково требование API OpenXml. Именно поэтому для каждой ячейки параметры границ TableCellBorders инициализируются копией переменной cellBorders. Код внесения данных заказа в таблицу ниже:

var totalSum = 0m;
var counter = 1;
foreach (var purchasePosition in data.Items)
{
    var row = new TableRow(new TableRowProperties());

    var numberCell = new TableCell(new TableCellProperties { TableCellBorders = (TableCellBorders)cellBorders.CloneNode(true) });
    SetCellText(numberCell, counter++.ToString(), JustificationValues.Center);
    row.Append(numberCell);

    var codeCell = new TableCell(new TableCellProperties { TableCellBorders = (TableCellBorders)cellBorders.CloneNode(true) });
    SetCellText(codeCell, purchasePosition.ProductCode, JustificationValues.Center);
    row.Append(codeCell);

    var nameCell = new TableCell(new TableCellProperties  { TableCellBorders = (TableCellBorders)cellBorders.CloneNode(true) });
    SetCellText(nameCell, purchasePosition.ProductName, JustificationValues.Left);
    row.Append(nameCell);

    var countCell = new TableCell(new TableCellProperties { TableCellBorders = (TableCellBorders)cellBorders.CloneNode(true) });
    SetCellText(countCell, purchasePosition.Count.ToString(), JustificationValues.Center);
    row.Append(countCell);

    var priceCell = new TableCell(new TableCellProperties { TableCellBorders = (TableCellBorders)cellBorders.CloneNode(true) });
    SetCellText(priceCell, $"{purchasePosition.UnitPrice} руб.", JustificationValues.Center);
    row.Append(priceCell);

    var sum = purchasePosition.Count * purchasePosition.UnitPrice;
    totalSum += sum;
    var sumCell = new TableCell(new TableCellProperties { TableCellBorders = (TableCellBorders)cellBorders.CloneNode(true) });
    SetCellText(sumCell, $"{sum} руб.", JustificationValues.Center);
    row.Append(sumCell);

    wordTable.Append(row);
}

Добавление строки итогов

Для вывода суммы заказа добавим в таблицу еще одну строку и добавим ей объединение ячеек по столбцам, как показано ниже:

var totalCell = new TableCell(new TableCellProperties
{
    TableCellBorders = (TableCellBorders)cellBorders.CloneNode(true),
    GridSpan = new GridSpan { Val = 6 }
});

SetCellText(totalCell, $"Общая сумма заказа: {totalSum} руб.", JustificationValues.Right, bold: true);

var totalRow = new TableRow(new TableRowProperties(), totalCell);
wordTable.Append(totalRow);

Горизонтальное объединение ячеек задается с помощью свойства TableCellProperties.GridSpan с указанием количества столбцов, которые входят в объединение. В нашем случае это все шесть столбцов таблицы.

Если бы нам требовалось вертикальное объединение ячеек, для этого мы использовали бы свойство TableCellProperties.VerticalMerge. Однако, в отличие от GridSpan, в VerticalMerge уже не задается количество строк, входящих в объединение. В вертикальное объединение попадут все строки, находящиеся под выбранной ячейкой, вплоть до новой ячейки, в которой будет задано свойство VerticalMerge со значением Val=MergedCellValues.Restart.

Заключение

В этой статье мы добавили таблицу в наш печатный документ заказа в интернет-магазине. Готовый документ выглядит так:

Разрабатываем печать документов на .NET с помощью OpenXml. Часть 2 - 2

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

На этом наш демонстрационный docx-документ завершен, а вместе с ним и обзор основных возможностей печати docx с помощью DocumentFormat.OpenXml. Безусловно, здесь демонстрируется небольшая часть возможностей, доступных в спецификации OpenXML. Формат поддерживает как целые разделы элементов, не вошедшие в наш обзор (например, рисованные фигуры и диаграммы), так и разнообразные настройки упомянутых элементов: текста, таблиц, изображений. Однако в наших статьях мы постарались охватить наиболее популярное содержимое типичного документа — текст, таблицы и изображения, а также основные приемы работы с ними в .NET.

С исходным кодом всего проекта можно ознакомиться по ссылке. А в заключительной части нашего цикла статей об использовании DocumentFormat.OpenXml мы рассмотрим еще один распространённый Use-case – печать таблиц в xlsx.


Мы в «БАРС Груп» разрабатываем цифровые решения для государства, бизнеса и людей. Принимаем активное участие в реализации Национального проекта «Цифровая экономика» и создаем цифровые решения для импортозамещения программного обеспечения – 88 решений компании зарегистрировано в реестре российского ПО. Рассказываем о наших продуктах и ИТ-трендах в Telegram-канале. Сервис печати Sprinter  также входит в реестр ПО. Он помогает разработчикам и аналитикам с печатью документов по заданным шаблонам и благодаря ему увидела свет эта статья!

Автор: barsgroup_blog

Источник

Оставить комментарий