При разработке печатных форм в 1С:Предприятии часто возникает задача визуального объединения строк по определенному признаку. Например, если у нас выводятся комплектующие детали для одного основного номенклатурного узла, мы хотим, чтобы в первой колонке наименование основного узла было объединено на все пять строк комплектующих. Это делает документ читаемым и профессиональным.
В этой статье мы подробно разберем, как реализовать такое объединение программно. Мы проанализируем три различных подхода, рассмотрим их преимущества и недостатки, изучим конкретные примеры кода, а также разберем подводные камни, связанные с производительностью, автовысотой строк и экспортом в сторонние форматы вроде Excel.
Прежде чем переходить к коду, давайте разберем теорию. В платформе 1С табличный документ представляет собой сетку координат. Мы не можем объединить ячейки в макете статически, если состав строк у нас динамический. Объединение должно выполняться строго программно.
Важно помнить ключевое правило работы с табличным документом: объединять можно только те ячейки, которые уже выведены в итоговый табличный документ. Вы не можете объединить ячейки в самом макете-шаблоне до вывода, если их количество заранее неизвестно. Сначала мы выводим секции (строки) в результирующий документ, и только затем указываем платформе, какую область нужно объединить.
Для программного объединения используется метод Объединить() объекта ОбластьЯчеекТабличногоДокумента. Чтобы получить эту область, нам нужно знать точные координаты: номер первой строки, номер первой колонки, номер последней строки и номер последней колонки.
---
Этот метод часто используется разработчиками. Суть его заключается в том, что в процессе обхода выборки данных или таблицы значений мы отслеживаем момент смены группировки (например, когда меняется основное наименование). Мы запоминаем строку, с которой началась текущая группа, а при смене группы — вычисляем текущую высоту таблицы и производим объединение ячеек для предыдущей группы.
Давайте проанализируем рабочий пример кода, основанный на решении автора темы, но избавленный от жесткого кодирования (хардкода) начальной высоты:
// Создаем или получаем табличный документ
ТабДок = Новый ТабличныйДокумент;
// Выводим шапку таблицы
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ТабДок.Вывести(ОбластьШапка);
// Получаем область строки для вывода данных
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
// Инициализируем переменные для отслеживания группировки
ТекНаименованиеОсновное = "";
НачалоГруппы = 0;
НомПП = 1;
Для Каждого СтрТз Из ДокументОбъект.Составляющие Цикл
// Заполняем параметры строки данными
ОбластьСтрока.Параметры.НомПП = НомПП;
ОбластьСтрока.Параметры.НаименованиеОсновное = СтрТз.НаименованиеОсновное;
ОбластьСтрока.Параметры.Наименование = СтрТз.Наименование;
ОбластьСтрока.Параметры.Количество = СтрТз.Количество;
ОбластьСтрока.Параметры.Цена = СтрТз.Цена;
ОбластьСтрока.Параметры.Сумма = СтрТз.Сумма;
// Проверяем, изменилась ли группа
Если СтрТз.НаименованиеОсновное <> ТекНаименованиеОсновное Тогда
// Если это не первая строка и у нас накопилась группа для объединения
Если НачалоГруппы > 0 И (ТабДок.ВысотаТаблицы > НачалоГруппы) Тогда
// Получаем область первой колонки (где выводится НаименованиеОсновное) от начала группы до конца выведенной части
ОбластьДляОбъединения = ТабДок.Область(НачалоГруппы, 1, ТабДок.ВысотаТаблицы, 1);
ОбластьДляОбъединения.Объединить();
КонецЕсли;
// Фиксируем новое значение группировки
ТекНаименованиеОсновное = СтрТз.НаименованиеОсновное;
// Запоминаем строку, в которую сейчас будет выведена первая строка новой группы
НачалоГруппы = ТабДок.ВысотаТаблицы + 1;
КонецЕсли;
// Выводим строку в табличный документ
ТабДок.Вывести(ОбластьСтрока);
НомПП = НомПП + 1;
КонецЦикла;
// Не забываем объединить последнюю группу после завершения цикла
Если НачалоГруппы > 0 И (ТабДок.ВысотаТаблицы > НачалоГруппы) Тогда
ОбластьДляОбъединения = ТабДок.Область(НачалоГруппы, 1, ТабДок.ВысотаТаблицы, 1);
ОбластьДляОбъединения.Объединить();
КонецЕсли;
Важное примечание по коду: Обратите внимание на расчет переменной НачалоГруппы. Мы присваиваем ей значение ТабДок.ВысотаТаблицы + 1 непосредственно перед выводом первой строки новой группы. Это избавляет нас от привязки к жестким номерам строк (как было у автора темы с ТекВысота = 41), делая алгоритм динамическим и устойчивым к изменениям высоты шапки документа.
---
Малоизвестный, но крайне элегантный факт: методы Вывести() и Присоединить() табличного документа являются функциями. Они возвращают объект типа ОбластьЯчеекТабличногоДокумента, который представляет собой область, только что добавленную в результирующий документ.
Это позволяет нам полностью отказаться от ручного расчета высоты таблицы через ТабДок.ВысотаТаблицы + 1. Мы можем получать координаты "верха" и "низа" выведенных строк напрямую из свойств возвращаемой области.
Рассмотрим, как переписать наш алгоритм с использованием этого подхода:
ТекНаименованиеОсновное = "";
СтрокаНачалаКоординат = 0;
Для Каждого СтрТз Из ДокументОбъект.Составляющие Цикл
// Заполнение параметров области строки
ОбластьСтрока.Параметры.НаименованиеОсновное = СтрТз.НаименованиеОсновное;
ОбластьСтрока.Параметры.Наименование = СтрТз.Наименование;
// ... заполнение остальных параметров ...
// Если группа сменилась, нам нужно объединить предыдущую группу ячеек
Если СтрТз.НаименованиеОсновное <> ТекНаименованиеОсновное Тогда
Если СтрокаНачалаКоординат > 0 И (ТабДок.ВысотаТаблицы > СтрокаНачалаКоординат) Тогда
ОбластьГруппы = ТабДок.Область(СтрокаНачалаКоординат, 1, ТабДок.ВысотаТаблицы, 1);
ОбластьГруппы.Объединить();
КонецЕсли;
ТекНаименованиеОсновное = СтрТз.НаименованиеОсновное;
// Выводим строку и сразу получаем ее координаты в результирующем документе
ВыведеннаяСтрока = ТабДок.Вывести(ОбластьСтрока);
СтрокаНачалаКоординат = ВыведеннаяСтрока.Верх; // Запоминаем верхнюю границу выведенной строки
Иначе
// Если это продолжение группы, просто выводим строку
ТабДок.Вывести(ОбластьСтрока);
КонецЕсли;
КонецЦикла;
// Объединение последней группы
Если СтрокаНачалаКоординат > 0 И (ТабДок.ВысотаТаблицы > СтрокаНачалаКоординат) Тогда
ОбластьГруппы = ТабДок.Область(СтрокаНачалаКоординат, 1, ТабДок.ВысотаТаблицы, 1);
ОбластьГруппы.Объединить();
КонецЕсли;
Этот способ более безопасен с точки зрения объектной модели 1С, так как мы работаем непосредственно со свойствами выведенного объекта (ВыведеннаяСтрока.Верх и ВыведеннаяСтрока.Низ).
---
Наиболее надежный архитектурный подход — разделение логики формирования данных и логики оформления. Сначала мы выводим таблицу "как есть", просто дублируя значение группировки в каждой строке. После того как весь табличный документ полностью сформирован, мы запускаем процедуру постобработки, которая сканирует колонку сверху вниз и объединяет ячейки с одинаковыми значениями.
Разберем готовый универсальный алгоритм постобработки:
Процедура ОбъединитьОдинаковыеЯчейкиВКолонке(ТабДок, НомерКолонки, СтрокаНачала)
ПоследняяСтрока = ТабДок.ВысотаТаблицы;
Если ПоследняяСтрока <= СтрокаНачала Тогда
Возврат; // Таблица пустая или содержит всего одну строку
КонецЕсли;
НачалоОбъединения = СтрокаНачала;
Для ТекущаяСтрока = СтрокаНачала + 1 По ПоследняяСтрока Цикл
ЗначениеТекущее = ТабДок.Область(ТекущаяСтрока, НомерКолонки).Текст;
ЗначениеПредыдущее = ТабДок.Область(ТекущаяСтрока - 1, НомерКолонки).Текст;
Если ЗначениеТекущее = ЗначениеПредыдущее И Не ПустаяСтрока(ЗначениеТекущее) Тогда
// Если значения совпадают, продолжаем обход группы
Продолжить;
Иначе
// Значение изменилось, объединяем накопленный диапазон
Если ТекущаяСтрока - 1 > НачалоОбъединения Тогда
ОбластьДляОбъединения = ТабДок.Область(НачалоОбъединения, НомерКолонки, ТекущаяСтрока - 1, NumberКолонки);
ОбластьДляОбъединения.Объединить();
КонецЕсли;
// Сбрасываем маркер начала объединения на новую строку
НачалоОбъединения = ТекущаяСтрока;
КонецЕсли;
КонецЦикла;
// Объединяем оставшийся хвост таблицы
Если ПоследняяСтрока > НачалоОбъединения Тогда
ОбластьДляОбъединения = ТабДок.Область(НачалоОбъединения, НомерКолонки, ПоследняяСтрока, НомерКолонки);
ОбластьДляОбъединения.Объединить();
КонецЕсли;
КонецПроцедуры
Вызов этой процедуры крайне прост. Вы сначала формируете табличный документ обычным циклом без усложнения его кодом группировок, а в самом конце перед выводом на экран вызываете:
// Предположим, строки данных начинаются с 10-й строки табличного документа
ОбъединитьОдинаковыеЯчейкиВКолонке(ТабДок, 1, 10);
Почему этот подход считается лучшим? Во-первых, ваш основной цикл вывода данных остается чистым, его легко читать и поддерживать. Во-вторых, при объединении ячеек в 1С платформа автоматически сохраняет значение только левой верхней ячейки объединяемой области, а значения остальных ячеек стирает, что избавляет вас от необходимости вручную очищать дублирующие значения в цикле.
---
Использование вертикального объединения ячеек таит в себе несколько неочевидных проблем, о которых необходимо знать заранее.
Если в объединенную по вертикали ячейку выводится длинный текст, который должен переноситься по словам, вы столкнетесь с неприятной особенностью платформы 1С: свойство АвтоВысотаСтроки не работает для вертикально объединенных областей. Платформа просто не умеет динамически рассчитывать высоту нескольких строк под объем перенесенного текста.
Решение: Либо гарантировать, что выводимый текст короткий и точно поместится в одну строку, либо задавать фиксированную высоту строк с запасом в свойствах макета, либо отказаться от вертикального объединения в пользу горизонтальных группировок.
Метод Объединить() заставляет движок отрисовки табличного документа каждый раз пересчитывать геометрию всей таблицы. Если у вас в отчете выводятся тысячи строк, частое обращение к методу Объединить() приведет к сильному падению производительности («зависанию» формирования отчета).
Решение: Если строк очень много, используйте стандартные механизмы группировки строк платформы с помощью методов ТабДок.НачатьГруппуСтрок() и ТабДок.ЗакончитьГруппуСтрок(). Это работает значительно быстрее и позволяет пользователям сворачивать и разворачивать блоки данных.
Пользователи очень любят сохранять печатные формы в формат .xlsx для последующего анализа. Однако таблицы с объединенными ячейками в Excel доставляют массу неудобств:
ВПР / VLOOKUP)..mxl в .xlsx объединенные по вертикали ячейки часто теряют правильное отображение высоты, из-за чего текст обрезается.Рекомендация: Хорошей практикой считается добавление на форму отчета или в настройки печатной формы флажка "Объединять одинаковые ячейки". Если пользователь хочет распечатать красивый документ — он включает флажок. Если планирует выгрузить данные в Excel для анализа — выключает, получая плоскую и удобную для обработки таблицу.
---
В сообщениях темы упоминается свойство ПоВыделеннымКолонкам. Важно понимать разницу между физическим объединением и этим свойством. Оно применимо только для горизонтального выравнивания (например, в шапках документов).
Если вы установите в свойствах ячеек горизонтальное положение По выделенным колонкам, текст визуально распределится по ширине всех выделенных колонок, но физически ячейки останутся раздельными. Это позволяет пользователю скопировать данные одной из колонок, не захватывая соседние, что невозможно сделать, если ячейки объединены физически с помощью метода Объединить(). К сожалению, для вертикального выравнивания аналогичного свойства не существует, поэтому для объединения строк по вертикали метод Объединить() остается единственным рабочим инструментом.