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