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