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