Работа с иерархическими структурами данных, такими как Дерево значений, является повседневной задачей разработчика 1С. Однако программное управление состоянием узлов (развертывание и сворачивание) имеет свои тонкости в зависимости от типа форм (обычные или управляемые) и версии платформы. В данной статье мы подробно разберем, как управлять визуальным представлением дерева, проанализируем различные подходы — от классической рекурсии до использования библиотек стандартных подсистем (БСП) и оптимизационных «хаков».
Прежде чем переходить к коду, выясним ключевое различие в работе с деревом. В обычном приложении мы работаем непосредственно со строкой табличного поля. В управляемом приложении методы управления состоянием (Развернуть(), Свернуть()) принадлежат элементу формы типа ТаблицаФормы, а в качестве параметра они принимают не саму строку, а её идентификатор.
В обычном интерфейсе методы вызываются у элемента управления ТабличноеПоле. Рассмотрим пример, как развернуть все выделенные строки или свернуть все строки первого уровня:
// Развернуть выделенные строки
Для каждого Строка Из ЭлементыФормы.ТабличноеПоле.ВыделенныеСтроки Цикл
ЭлементыФормы.ТабличноеПоле.Развернуть(Строка, Истина); // Истина - развернуть с подчиненными
КонецЦикла;
// Свернуть все корневые строки дерева
Для каждого Строка Из ЭлементыФормы.ТабличноеПоле.Значение.Строки Цикл
ЭлементыФормы.ТабличноеПоле.Свернуть(Строка);
КонецЦикла;
Обратите внимание на параметр Истина в методе Развернуть. Он позволяет рекурсивно раскрыть всю ветку. У метода Свернуть такого параметра нет, поэтому для полной свертки иерархии на большую глубину потребуется рекурсивный обход.
В управляемых формах работа строится иначе. Нам необходимо получить идентификатор строки коллекции. Рассмотрим самый простой способ развернуть все узлы дерева одним вызовом:
&НаКлиенте
Процедура РазвернутьВсеУзлы(Команда)
// Разворачиваем корень (индекс 0) со всеми подчиненными
Элементы.ДеревоДанных.Развернуть(0, Истина);
КонецПроцедуры
Однако этот метод не всегда срабатывает корректно, если дерево динамически обновляется. В таких случаях разберем универсальный рекурсивный алгоритм, который гарантированно обходит все уровни:
&НаКлиенте
Процедура УправлениеДеревом(Развернуть = Истина)
КоллекцияЭлементов = ДеревоДанных.ПолучитьЭлементы();
ВыполнитьОбход(КоллекцияЭлементов, Развернуть);
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьОбход(Коллекция, Развернуть)
Для каждого Элемент Из Коллекция Цикл
Идентификатор = Элемент.ПолучитьИдентификатор();
Если Развернуть Тогда
Элементы.ДеревоДанных.Развернуть(Идентификатор, Истина);
Иначе
// Для сворачивания нужно сначала обработать вложенные уровни
Подчиненные = Элемент.ПолучитьЭлементы();
Если Подчиненные.Количество() > 0 Тогда
ВыполнитьОбход(Подчиненные, Развернуть);
КонецЕсли;
Элементы.ДеревоДанных.Свернуть(Идентификатор);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Важный нюанс при сворачивании: проанализируем ситуацию в сообщении №31. Практика показывает, что на некоторых версиях платформы (например, 8.3.18) узел может не свернуться, если сначала не были свернуты его дочерние элементы. Поэтому в примере выше при Развернуть = Ложь мы сначала уходим в рекурсию и только потом вызываем Свернуть() для родителя.
Если ваша конфигурация построена на базе БСП, не стоит изобретать велосипед. Проанализируем возможности общего модуля СтандартныеПодсистемыКлиент. В нем существует готовый метод, который учитывает все особенности платформы:
// Развернуть дерево полностью
СтандартныеПодсистемыКлиент.РазвернутьУзлыДерева(ЭтаФорма, "ИмяЭлементаДерево", "*", Истина);
Параметры процедуры позволяют гибко настроить, какие именно узлы нужно раскрыть. Это наиболее стабильный и рекомендуемый способ для современных конфигураций.
Иногда при очень большом количестве строк (десятки тысяч) рекурсивный обход на клиенте может вызвать «подтормаживание» интерфейса. Выясним, как можно ускорить этот процесс, используя особенности платформы.
Способ 1: Манипуляция видимостью и начальным отображением. Посмотрим на интересный прием: если временно изменить свойство НачальноеОтображениеДерева и переключить видимость, платформа принудительно перерисует дерево в нужном состоянии:
&НаКлиенте
Процедура БыстроеРазвертывание()
ЭлементДерево = Элементы.ДеревоДанных;
ЭлементДерево.Видимость = Ложь;
// Меняем режим отображения
ЭлементДерево.НачальноеОтображениеДерева = НачальноеОтображениеДерева.РаскрыватьВсеУровни;
// Хак: заставляем платформу думать, что это первичная отрисовка
РазвернутьНаСервере();
ЭлементДерево.Видимость = Истина;
КонецПроцедуры
&НаСервере
Процедура РазвернутьНаСервере()
// Перезаполняем данные из реквизита в значение и обратно
Дерево = РеквизитФормыВЗначение("ДеревоДанных");
ЗначениеВРеквизитФормы(Дерево, "ДеревоДанных");
КонецПроцедуры
Способ 2: Использование обработчика ожидания. Проанализируем проблему из сообщения №46: при открытии формы методы свертки могут не отрабатывать, так как дерево еще не «отрисовано». В этом случае используйте ПодключитьОбработчикОжидания с минимальным интервалом (0.1 сек), чтобы выполнить код сразу после завершения инициализации формы.
Подведем итог и сформулируем основные рекомендации для работы с деревьями значений:
Развернуть() непосредственно у табличного поля, передавая саму строку.ПолучитьИдентификатор().Элементы.ВашеДерево.Развернуть(0, Истина).СтандартныеПодсистемыКлиент.РазвернутьУзлыДерева — это сэкономит время и избавит от ошибок.НачальноеОтображениеДерева.Рассмотренные нами методы позволяют гибко управлять интерфейсом, обеспечивая пользователю максимально комфортную работу с иерархическими данными в системе 1С:Предприятие.