Вывод иерархических структур данных, таких как ДеревоЗначений, в табличный документ — классическая задача в разработке на платформе 1С. Основная сложность здесь заключается в том, чтобы не просто вывести строки на экран, а сохранить визуальную иерархию (с возможностью сворачивать и разворачивать узлы с помощью стандартных кнопок плюса и минуса). При неверной настройке уровней вложенности или неправильном вызове методов группировки структура отчета сбивается, а группы перестают работать.
В этой статье мы подробно разберем, почему возникают ошибки при рекурсивном выводе дерева значений, детально проанализируем механизмы автоматической и ручной группировки строк, а также рассмотрим примеры корректного кода для решения этой задачи.
Рассмотрим исходный алгоритм, с которым столкнулся автор темы. Проблема заключалась в том, что при выводе дерева строк группировки либо строились некорректно, либо вовсе не отображались. Давайте проанализируем проблемный участок кода:
Функция ВывестиДеревеЗначений(мСтрокиДерева, Макет, ТабДок, Уровень)
ОбластьСтроки = Макет.ПолучитьОбласть("Строка1");
Для Каждого СтрокаНоменклатуры Из мСтрокиДерева.Строки Цикл
ОбластьСтроки.Параметры.Номенклатура = СтрокаНоменклатуры.Номенклатура;
ОбластьСтроки.Параметры.КоличествоУпаковок = СтрокаНоменклатуры.КоличествоУпаковок;
ТабДок.Вывести(ОбластьСтроки, Уровень);
ВывестиДеревеЗначений(СтрокаНоменклатуры, Макет, ТабДок, СтрокаНоменклатуры.Уровень() + 1);
КонецЦикла;
КонецФункции
В данном фрагменте кода кроются сразу несколько критических ошибок, которые ломают логику построения дерева группировок в 1С:
Строка.Уровень() возвращает индекс уровня, начинающийся с 0 (для корневых элементов). В то же время метод табличного документа Вывести(Область, Уровень) ожидает уровень группировки, начинающийся с 1. Если передать туда 0, группировка построена не будет.НачатьАвтогруппировкуСтрок() и ЗакончитьАвтогруппировкуСтрок() нельзя вызывать внутри тела рекурсивной функции на каждой итерации или для каждого узла. Это ломает внутренние механизмы сопоставления уровней платформы.Ниже мы подробно разберем два надежных способа вывода дерева значений с группировками: автоматический и ручной.
---
Метод автоматической группировки строк основан на передаче числового значения уровня в метод Вывести() табличного документа. Платформа 1С самостоятельно анализирует последовательность выводимых уровней и строит иерархические связи.
Для того чтобы автогруппировка отработала корректно, необходимо соблюдать три важнейших правила:
НачатьАвтогруппировкуСтрок() должен вызываться строго один раз непосредственно перед началом вывода дерева (например, сразу после вывода шапки отчета).ЗакончитьАвтогруппировкуСтрок() вызывается строго один раз после полного завершения работы рекурсивной функции.Вывести(), должен быть на единицу больше, чем системный уровень строки дерева значений: Уровень = СтрокаДерева.Уровень() + 1.Проанализируем правильный шаблон реализации автоматической группировки. Код вызова процедуры заполнения выглядит следующим образом:
// Внешний код, инициирующий вывод отчета
ТабДок.Очистить();
ВывестиШапкуТаблицы(ТабДок, Макет);
// Инициализируем автогруппировку
ТабДок.НачатьАвтогруппировкуСтрок();
// Запускаем рекурсивный обход корневых строк дерева значений
// В качестве начального уровня передаем 1
РекурсивныйВыводДерева(ДеревоЗначений.Строки, Макет, ТабДок);
// Завершаем автогруппировку
ТабДок.ЗакончитьАвтогруппировкуСтрок();
А теперь детально разберем саму рекурсивную процедуру вывода строк:
Процедура РекурсивныйВыводДерева(КоллекцияСтрок, Макет, ТабДок) Export
ОбластьСтроки = Макет.ПолучитьОбласть("Строка");
Для Каждого СтрокаДерева Из КоллекцияСтрок Цикл
// Заполняем параметры области макета
ЗаполнитьЗначенияСвойств(ОбластьСтроки.Параметры, СтрокаДерева);
// Вычисляем уровень для табличного документа.
// Так как Уровень() для корня возвращает 0, прибавляем 1.
ТекущийУровень = СтрокаДерева.Уровень() + 1;
// Выводим строку с указанием уровня вложенности
ТабДок.Вывести(ОбластьСтроки, ТекущийУровень);
// Если у строки есть подчиненные элементы, вызываем рекурсию для них
Если СтрокаДерева.Строки.Количество() > 0 Тогда
РекурсивныйВыводДерева(СтрокаДерева.Строки, Макет, ТабДок);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
При использовании данного способа платформа автоматически свяжет иерархию. Нам не требуется вручную открывать и закрывать группы для каждой конкретной ветки.
---
Как показывает практика разработки (и это подтверждается в обсуждении темы на форуме), автоматическая группировка в сложных макетах или при наличии пустых уровней может вести себя непредсказуемо. В таких ситуациях наилучшим решением является ручное управление границами групп с помощью методов НачатьГруппуСтрок() и ЗакончитьГруппуСтрок().
При использовании ручной группировки нам больше не нужно передавать уровень в метод Вывести(). Вместо этого мы явно указываем платформе, в какой момент начинается подчиненная группа элементов и где она заканчивается.
Давайте разберем по шагам логику работы рекурсивного алгоритма с ручной группировкой:
Вывести() без указания числового уровня.Строки.ТабДок.НачатьГруппуСтрок().ТабДок.ЗакончитьГруппуСтрок().Ниже представлен детальный пример программного кода, реализующий данный подход:
Процедура ВывестиДеревоВручную(КоллекцияСтрок, Макет, ТабДок) Export
ОбластьСтроки = Макет.ПолучитьОбласть("Строка1");
Для Каждого СтрокаДерева Из КоллекцияСтрок Цикл
// Заполняем параметры области макета данными строки дерева
ОбластьСтроки.Параметры.Номенклатура = СтрокаДерева.Номенклатура;
ОбластьСтроки.Параметры.КоличествоУпаковок = СтрокаДерева.КоличествоУпаковок;
// Выводим строку в табличный документ без указания уровня
ТабДок.Вывести(ОбластьСтроки);
// Проверяем наличие вложенных строк
Если СтрокаДерева.Строки.Количество() > 0 Тогда
// Начинаем группу строк для подчиненных элементов
ТабДок.НачатьГруппуСтрок();
// Рекурсивно выводим подчиненную коллекцию
ВывестиДеревоВручную(СтрокаДерева.Строки, Макет, ТабДок);
// Обязательно закрываем группу строк
ТабДок.ЗакончитьГруппуСтрок();
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Важное преимущество ручного метода: Данный способ гарантирует стопроцентную стабильность структуры дерева группировок независимо от того, насколько сложной и нелинейной является иерархия ваших данных. Исключаются любые ошибки сдвигов и пропусков уровней.
---
Даже если ваш программный код написан абсолютно верно, группировки могут визуально не появиться на экране пользователя. Чтобы этого не произошло, необходимо принудительно задать настройки отображения для результирующего табличного документа.
Рассмотрим ключевые свойства, которые необходимо настроить перед выводом результата на форму:
// Включаем отображение кнопок группировок ("+" и "-") на панели табличного документа
ТабДок.ОтображатьГруппировки = Истина;
// Задаем расположение кнопок свертки групп (Истина - сверху группы, Ложь - снизу)
ТабДок.ОтображатьГруппировкиСверху = Истина;
// По умолчанию можно сразу свернуть дерево до определенного уровня, чтобы отчет не выглядел громоздким.
// Например, покажем только первый уровень группировки:
ТабДок.ПоказатьУровеньГруппировокСтрок(0);
Обратите внимание на метод ПоказатьУровеньГруппировокСтрок(). Он принимает индекс уровня (начиная с 0). Использование этого метода позволяет сразу преподнести пользователю аккуратный свернутый отчет с возможностью ручного раскрытия интересующих его узлов.
---
Чтобы дерево в печатной форме выглядело эстетично и интуитивно понятно, подчиненные элементы должны быть сдвинуты вправо относительно родительских. В платформе 1С для этого не нужно программно добавлять пробелы в наименование номенклатуры. Для этого существует стандартное свойство ячейки макета.
Выполните следующие шаги для настройки визуального сдвига:
Номенклатура).1 или 2).При выводе строки с указанием уровня (или при нахождении строки внутри ручной группы) платформа автоматически умножит числовое значение отступа на текущий уровень вложенности элемента и выполнит соответствующий сдвиг текста вправо. Это обеспечит классическое и аккуратное представление иерархии.