При формировании сложных печатных форм в 1С часто возникает задача вывести данные не в один вертикальный список, а в две параллельные колонки (например, для экономии места на листе или по требованию макета). Ситуация осложняется, когда каждый блок данных (условно «левый» и «правый») имеет динамическое и заранее неизвестное количество строк. Если использовать стандартный метод Присоединить(), платформа привязывает новую область к нижней строке последней выведенной секции, что при разной высоте блоков приводит к «разъезжанию» таблицы.
В этой статье мы подробно разберем несколько способов решения данной задачи, проанализируем их преимущества и программную реализацию.
Рассмотрим самый надежный программный метод. Его суть заключается в том, что мы не выводим сначала весь левый блок, а затем весь правый. Вместо этого мы выводим их параллельно, строку за строкой. Для этого нам необходимо заранее подготовить данные (например, поместить их в массивы или таблицы значений).
Проанализируем алгоритм по шагам:
Вывести() для левой части строки, затем Присоединить() для правой.Посмотрим на пример программной реализации этого подхода:
// Предположим, ДанныеСлева и ДанныеСправа - это массивы со строками
МаксСтрок = Макс(ДанныеСлева.Количество(), ДанныеСправа.Количество());
// Выводим шапки блоков
ОбластьШапкаЛ = Макет.ПолучитьОбласть("Шапка");
ОбластьШапкаП = Макет.ПолучитьОбласть("Шапка");
ОбластьШапкаЛ.Параметры.Имя = "Вася";
ОбластьШапкаП.Параметры.Имя = "Петя";
ТабДок.Вывести(ОбластьШапкаЛ);
ТабДок.Присоединить(ОбластьШапкаП);
// Выводим строки
Для НомерСтроки = 0 По МаксСтрок - 1 Цикл
// Работаем с левой колонкой
Если НомерСтроки < ДанныеСлева.Количество() Тогда
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
ОбластьСтрока.Параметры.Значение = ДанныеСлева[НомерСтроки].Значение;
ТабДок.Вывести(ОбластьСтрока);
Иначе
// Выводим пустую область, чтобы "закрыть" место слева
ОбластьПустая = Макет.ПолучитьОбласть("ПустаяСтрокаЛево");
ТабДок.Вывести(ОбластьПустая);
КонецЕсли;
// Работаем с правой колонкой через Присоединить
Если НомерСтроки < ДанныеСправа.Количество() Тогда
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
ОбластьСтрока.Параметры.Значение = ДанныеСправа[НомерСтроки].Значение;
ТабДок.Присоединить(ОбластьСтрока);
Иначе
// Присоединяем пустую область справа
ОбластьПустая = Макет.ПолучитьОбласть("ПустаяСтрокаПраво");
ТабДок.Присоединить(ОбластьПустая);
КонецЕсли;
КонецЦикла;
Этот метод гарантирует, что следующая пара блоков (например, «Маша» и «Коля») начнется строго под самым длинным предыдущим блоком, так как высота левой и правой части была программно выровнена.
Если структура данных позволяет, мы можем переложить логику разделения на колонки на плечи SQL-движка 1С. Разберем ситуацию, когда нам нужно вывести список записей в два столбца. Мы можем пронумеровать записи в запросе, разделить их на «четные» и «нечетные» и соединить их горизонтально.
Для этого в языке запросов 1С v8.3 удобно использовать функцию АВТОНОМЕРЗАПИСИ(). Рассмотрим пример логики запроса:
// 1. Получаем все данные с порядковым номером
ВЫБРАТЬ
ТЧ.Номенклатура,
АВТОНОМЕРЗАПИСИ() КАК ПорядковыйНомер
ПОМЕСТИТЬ ВТ_ВсеДанные
ИЗ
Документ.Реализация.Товары КАК ТЧ;
// 2. Выбираем нечетные (левая колонка)
ВЫБРАТЬ
ВТ.Номенклатура КАК ИмяЛево,
(ВТ.ПорядковыйНомер + 1) / 2 КАК Группа
ПОМЕСТИТЬ ВТ_Лево
ИЗ
ВТ_ВсеДанные КАК ВТ
ГДЕ
ВТ.ПорядковыйНомер % 2 <> 0;
// 3. Выбираем четные (правая колонка)
ВЫБРАТЬ
ВТ.Номенклатура КАК ИмяПраво,
ВТ.ПорядковыйНомер / 2 КАК Группа
ПОМЕСТИТЬ ВТ_Право
ИЗ
ВТ_ВсеДанные КАК ВТ
ГДЕ
ВТ.ПорядковыйНомер % 2 = 0;
// 4. Соединяем по виртуальному номеру группы
ВЫБРАТЬ
Л.ИмяЛево,
П.ИмяПраво
ИЗ
ВТ_Лево КАК Л
ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Право КАК П ПО Л.Группа = П.Группа;
После такого запроса нам достаточно обойти результат в одном цикле, используя одну область макета, в которой уже нарисованы две колонки. Метод Присоединить() в этом случае вообще не понадобится, так как одна строка выборки сразу содержит данные для обеих сторон.
Выясним причину, по которой иногда проще использовать «буферные» документы. Если алгоритм заполнения блоков очень сложный, с множеством условий и вложенных группировок, рассчитывать «максимальную высоту» в основном коде становится трудно. В этом случае мы можем применить стратегию «разделяй и властвуй»:
ТабличныйДокумент (назовем их БуферЛево и БуферПраво).БуферЛево всеми данными, которые должны идти в первую колонку (с заголовками, строками и итогами).БуферПраво.Для переноса удобно использовать метод ВставитьОбласть(). Проанализируем пример кода для объединения:
// Перенос данных из буферов в основной ТабДок
Для НомерСтроки = 1 По Макс(БуферЛево.ВысотаТаблицы, БуферПраво.ВысотаТаблицы) Цикл
Если НомерСтроки <= БуферЛево.ВысотаТаблицы Тогда
// Получаем область из одной строки левого буфера
ОбластьИзБуфера = БуферЛево.Область(НомерСтроки, 1, НомерСтроки, БуферЛево.ШиринаТаблицы);
ТабДок.Вывести(ОбластьИзБуфера);
Иначе
// Если левый буфер короче, выводим пустую строку нужной ширины
// (или просто пропускаем, если будем использовать координаты)
КонецЕсли;
Если НомерСтроки <= БуферПраво.ВысотаТаблицы Тогда
// Присоединяем область из правой колонки буфера
ОбластьИзБуфера = БуферПраво.Область(НомерСтроки, 1, НомерСтроки, БуферПраво.ШиринаТаблицы);
ТабДок.Присоединить(ОбластьИзБуфера);
КонецЕсли;
КонецЦикла;
Преимущество данного метода заключается в том, что 1С корректно переносит форматирование, ширину колонок и объединение ячеек из буферных документов. Это позволяет верстать левую и правую части независимо друг от друга.
Чтобы отчет выглядел профессионально, при реализации «двухколоночного» вывода рекомендуем обратить внимание на следующие моменты:
ПустаяСтрока. Она должна иметь те же границы (рамки) и шрифт, что и обычная строка данных, но без параметров. Это необходимо для заполнения «пустот», если один блок короче другого.Рассмотренные подходы позволяют гибко управлять выводом в ТабличныйДокумент, избегая наложения областей и обеспечивая ровное расположение блоков «шахматкой» или параллельными колонками вне зависимости от объема данных в каждой из них.