Разработка и Конфигурация · 10 мин
Как заполнить дерево значений на управляемой форме в 1С
Практическое руководство по заполнению дерева значений на управляемой форме 1С: реквизиты формы, рекурсия, построитель запроса, типовые ошибки и примеры кода.
Дерево значений на управляемой форме применяется там, где данные имеют иерархическую структуру: группы номенклатуры, подразделения, папки файлов, маршруты согласования. Главная сложность в том, что управляемая форма работает в клиент-серверном режиме, и данные дерева нужно передавать через реквизит формы, а не через произвольный объект, созданный на сервере.
Ключевой момент, который упускают начинающие разработчики: объект ДеревоЗначений, созданный оператором Новый ДеревоЗначений, нельзя напрямую присвоить реквизиту формы. Нужно использовать метод ЗначениеВРеквизитФормы, который преобразует прикладной объект в данные формы. Без этого шага дерево на форме останется пустым, хотя в коде ошибок не возникнет.
Ниже разобран полный цикл: создание реквизита формы, заполнение дерева из запроса, рекурсивное построение иерархии, отображение на форме и типовые ошибки с решениями.
Создание реквизита формы типа «Дерево значений»
Откройте форму в конфигураторе, перейдите на вкладку «Реквизиты» и добавьте новый реквизит, например ДеревоНоменклатуры, с типом ДеревоЗначений. После этого в подчинённой ветке реквизита добавьте колонки дерева: например, Ссылка (тип СправочникСсылка.Номенклатура), Наименование (строка), ЭтоГруппа (булево). Колонки реквизита определяют, какие данные будут доступны в каждой строке дерева.
Важно понимать различие: сам реквизит хранит данные формы, а не «живой» объект дерева. При обращении к нему из серверного кода нужно сначала получить объект через РеквизитФормыВЗначение("ДеревоНоменклатуры"), работать с ним, а затем вернуть обратно через ЗначениеВРеквизитФормы. Каждый такой вызов сериализует данные, поэтому частые обращения в цикле сильно замедляют форму.
Отображение дерева на форме
Перетащите реквизит дерева из панели реквизитов в дерево элементов формы — платформа автоматически создаст таблицу формы, связанную с этим реквизитом. В свойствах таблицы проверьте два параметра: ПутьКДанным должен указывать на реквизит дерева, а Отображение должно быть установлено в значение Дерево. Если отображение оставить «Список», иерархия свернётся в плоскую таблицу.
Для колонок таблицы формы путь к данным указывается через точку: ДеревоНоменклатуры.Наименование. Если колонки не добавились автоматически, добавьте их вручную и привяжите к соответствующим колонкам реквизита. Обычно в дереве оставляют видимой одну-две колонки, остальные используют как служебные (ссылки, флаги, идентификаторы родителя).
Заполнение дерева запросом с итогами по иерархии
Самый простой способ заполнить дерево — запрос с итогами по иерархии. Метод Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией) возвращает готовое дерево значений, которое остаётся только перенести в реквизит формы.
&НаСервере
Процедура ЗаполнитьДеревоНаСервере()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ПометкаУдаления
|ИТОГИ ПО
| Номенклатура.Ссылка ТОЛЬКО ИЕРАРХИЯ";
Дерево = Запрос.Выполнить().Выгрузить(
ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);
ЗначениеВРеквизитФормы(Дерево, "ДеревоНоменклатуры");
КонецПроцедуры
У этого способа есть ограничение: колонки выгруженного дерева должны совпадать по именам с колонками реквизита формы, иначе ЗначениеВРеквизитФормы сопоставит только совпадающие. Также запрос с иерархией подходит только для иерархических справочников и планов видов характеристик; для произвольных иерархий (например, построенных по регистру связей) нужна рекурсия.
Рекурсивное заполнение произвольной иерархии
Когда иерархия хранится не в самом справочнике, а во внешней структуре (регистр сведений «подчинённость», таблица с полями «Родитель / Элемент»), дерево строят рекурсивным обходом. Сначала одним запросом выбирают все связи, затем рекурсивно добавляют строки, начиная с корневых элементов.
&НаСервере
Процедура ЗаполнитьРекурсивно()
Дерево = РеквизитФормыВЗначение("ДеревоНоменклатуры");
Дерево.Строки.Очистить();
ТаблицаСвязей = ПолучитьСвязи(); // запрос: Родитель, Элемент
ДобавитьСтроки(Дерево.Строки, ТаблицаСвязей,
Справочники.Номенклатура.ПустаяСсылка());
ЗначениеВРеквизитФормы(Дерево, "ДеревоНоменклатуры");
КонецПроцедуры
&НаСервере
Процедура ДобавитьСтроки(Строки, ТаблицаСвязей, Родитель)
Отбор = Новый Структура("Родитель", Родитель);
Для Каждого Строка Из ТаблицаСвязей.НайтиСтроки(Отбор) Цикл
НоваяСтрока = Строки.Добавить();
НоваяСтрока.Ссылка = Строка.Элемент;
НоваяСтрока.Наименование = Строка.Элемент.Наименование;
ДобавитьСтроки(НоваяСтрока.Строки, ТаблицаСвязей, Строка.Элемент);
КонецЦикла;
КонецПроцедуры
Рекурсия требует защиты от зацикливания: если в данных есть циклическая ссылка (элемент является родителем самого себя через цепочку), обход уйдёт в бесконечную рекурсию и завершится ошибкой переполнения стека. Перед обходом проверяйте данные на циклы или ограничивайте глубину рекурсии счётчиком.
Заполнение дерева при создании формы и обновление данных
Обычно дерево заполняют в обработчике ПриСозданииНаСервере — это гарантирует, что при открытии формы пользователь сразу видит данные. Вызов процедуры заполнения из этого обработчика выполняется на сервере, поэтому запросы и обращения к базе разрешены без дополнительных настроек.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
ЗаполнитьДеревоНаСервере();
КонецПроцедуры
Для обновления дерева по кнопке «Обновить» вызывайте ту же серверную процедуру из клиентского обработчика команды. После перезаполнения реквизита форма автоматически перерисует таблицу — дополнительных вызовов обновления интерфейса не требуется. Если нужно сохранить развёрнутые ветки, перед перезаполнением запомните ссылки развёрнутых строк, а после — разверните их через Элементы.ДеревоНоменклатуры.Развернуть(ИдентификаторСтроки).
- 1Добавьте реквизит формы типа ДеревоЗначений и колонки к нему
- 2Разместите на форме таблицу, связанную с реквизитом, и установите Отображение = Дерево
- 3В ПриСозданииНаСервере вызовите серверную процедуру заполнения
- 4На сервере получите объект дерева, заполните его запросом или рекурсией
- 5Перенесите результат через ЗначениеВРеквизитФормы
- 6Проверьте отображение иерархии и разворачивание веток
Частые ошибки и их признаки
Самая распространённая ошибка — попытка присвоить реквизиту результат Запрос.Выполнить().Выгрузить() напрямую, без ЗначениеВРеквизитФормы. Форма при этом открывается без ошибок, но дерево пустое. Вторая типовая проблема — несовпадение имён колонок выгруженного дерева и реквизита формы: данные «молча» теряются при переносе.
Ещё одна группа ошибок связана с контекстом выполнения: обращение к РеквизитФормыВЗначение из клиентской процедуры невозможно — эти методы доступны только на сервере. Поэтому вся логика заполнения должна быть в процедурах с директивой &НаСервере, а клиентские обработчики только вызывают их.
Проверка результата и производительность
После внедрения проверьте три вещи: дерево отображается иерархически (а не плоским списком), разворачивание веток показывает вложенные строки, а в служебных колонках присутствуют ссылки на элементы. Если форма открывается медленно, замерьте время запроса и время переноса данных — при больших объёмах основные потери обычно в сериализации при ЗначениеВРеквизитФормы.
Для больших иерархий (десятки тысяч строк) рассмотрите альтернативы: динамический список с иерархическим просмотром (он подгружает данные порциями) или отложенное заполнение веток при разворачивании. Полное дерево в реквизите формы целиком передаётся между клиентом и сервером при каждом вызове, что при больших объёмах заметно нагружает канал.
Как проверить результат
Строки отображаются вложенно, есть значки разворачивания
Все строки в одном уровне — проверьте свойство Отображение таблицы
Все колонки содержат данные из запроса
Часть колонок пуста — имена не совпадают с реквизитом
Форма открывается за приемлемое время
Заметная пауза — сократите объём или перейдите на динамический список
Сравнение вариантов
Подходит для иерархических справочников; минимум кода, но только для штатной иерархии
Подходит для произвольных связей; требует защиты от циклов и контроля глубины
Подходит для очень больших данных; не требует реквизита-дерева, но менее гибок в оформлении
Частые вопросы
Почему дерево на форме пустое, хотя запрос возвращает данные?
Чаще всего результат запроса не перенесён в реквизит формы: выгруженное дерево — это отдельный объект. Используйте ЗначениеВРеквизитФормы(Дерево, "ИмяРеквизита"). Также проверьте, что имена колонок выгрузки совпадают с именами колонок реквизита.
Можно ли заполнить дерево на клиенте без вызова сервера?
Технически можно добавлять строки через коллекцию ДанныеФормыДерево на клиенте, но получить данные из базы без сервера нельзя. На практике заполнение всегда делают на сервере, а на клиенте лишь корректируют отображение.
Как развернуть все ветки дерева после заполнения?
Переберите строки верхнего уровня реквизита и для каждой вызовите Элементы.ИмяТаблицы.Развернуть(Строка.ПолучитьИдентификатор(), Истина). Второй параметр включает разворачивание всех подчинённых уровней.
Как сохранить состояние развёрнутых веток при обновлении дерева?
Перед перезаполнением пройдите по строкам и соберите ссылки развёрнутых элементов в массив. После заполнения найдите строки по ссылкам и разверните их через метод Развернуть с идентификаторами строк.
Чем дерево значений отличается от динамического списка с иерархией?
Дерево значений — это данные, полностью загруженные в форму и хранящиеся в реквизите. Динамический список подгружает данные порциями по мере прокрутки и лучше подходит для больших объёмов, но не позволяет свободно редактировать структуру строк.
Что важно запомнить
Заполнение дерева на управляемой форме сводится к трём шагам: реквизит типа ДеревоЗначений с колонками, заполнение объекта на сервере запросом или рекурсией и перенос через ЗначениеВРеквизитФормы. Следите за совпадением имён колонок, защищайте рекурсию от циклов и для больших объёмов рассмотрите динамический список.