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

от автора

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

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

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

Для создания пустой таблицы в 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.

Заключение

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

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

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

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


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


ссылка на оригинал статьи https://habr.com/ru/articles/900920/