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