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