К статье
Практический материал

Разработка и Конфигурация · 14 мин

Как вывести дерево значений на форму 1С программно

Пошаговая инструкция: как программно создать реквизит формы типа ДеревоЗначений, добавить колонки, заполнить строки и вывести дерево на управляемую форму 1С.

Дерево значений на управляемой форме 1С нужно, когда данные имеют иерархию: группы номенклатуры с позициями, подразделения с сотрудниками, этапы проекта с задачами. В отличие от обычной таблицы значений, дерево требует не только создать реквизит формы, но и правильно преобразовать объект ДеревоЗначений в данные формы через РеквизитФормыВЗначение и ЗначениеВРеквизитФормы, иначе на форме появится пустая таблица или ошибка.

Главная сложность в том, что реквизит типа «ДеревоЗначений» нельзя просто присвоить — нужно заранее объявить его структуру (колонки с типами) в методе ИзменитьРеквизиты() при создании формы на сервере, а затем заполнить и отобразить. Ниже разобран полный рабочий сценарий: от добавления реквизита до сворачивания узлов и обработки интерактивных действий пользователя.

Когда нужен программный вывод дерева, а когда достаточно настройки в конфигураторе

Если структура дерева известна на этапе разработки и не меняется, проще добавить реквизит формы типа «ДеревоЗначений» прямо в конфигураторе, перетащить его на форму и заполнять в коде. Это стандартный путь для большинства форм обработок и документов.

Программное создание реквизита оправдано, когда состав колонок определяется динамически: например, пользователь выбирает набор выводимых полей, или дерево строится по произвольному запросу с заранее неизвестными итоговыми полями. Также программный подход нужен в расширениях, где изменять основную форму нельзя или нежелательно.

Оцените ситуацию заранее: динамическое создание реквизитов возможно только в обработчике ПриСозданииНаСервере, до обращения к данным формы. Если попытаться вызвать ИзменитьРеквизиты() позже, платформа выдаст ошибку о недопустимом моменте вызова.

Маршрут решения

Как выбрать подход

Состав колонок дерева фиксирован и известен при разработкеДобавьте реквизит ДеревоЗначений в конфигураторе и только заполняйте его в коде
Колонки зависят от настроек пользователя или результата запросаСоздавайте реквизит программно через ИзменитьРеквизиты() в ПриСозданииНаСервере
Нужна иерархия без редактирования, только просмотрРассмотрите динамический список с иерархией — он проще и быстрее на больших объёмах

Шаг 1. Создаём реквизит формы типа ДеревоЗначений

Динамическое добавление реквизита выполняется в три действия: формируется массив добавляемых реквизитов с описанием колонок, вызывается ИзменитьРеквизиты(), затем создаётся элемент формы для отображения. Все колонки дерева описываются объектами ОписаниеРеквизитаФормы через конструктор Новый РеквизитФормы().

&НаСервере

Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)

ДобавляемыеРеквизиты = Новый Массив;

ДобавляемыеРеквизиты.Добавить(Новый РеквизитФормы("ДеревоДанных",

Новый ОписаниеТипов("ДеревоЗначений")));

ДобавляемыеРеквизиты.Добавить(Новый РеквизитФормы("Наименование",

Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(150)), "ДеревоДанных"));

ДобавляемыеРеквизиты.Добавить(Новый РеквизитФормы("Количество",

Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)), "ДеревоДанных"));

ДобавляемыеРеквизиты.Добавить(Новый РеквизитФормы("Сумма",

Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)), "ДеревоДанных"));

ИзменитьРеквизиты(ДобавляемыеРеквизиты);

КонецПроцедуры

Обратите внимание на третий параметр конструктора Новый РеквизитФормы() — это путь к родительскому реквизиту. Для колонок дерева он обязательно равен имени самого дерева («ДеревоДанных»). Если путь не указать, реквизит попадёт в корень формы, и дерево не увидит свои колонки.

Шаг 2. Добавляем элемент формы для отображения дерева

После создания реквизита нужен визуальный элемент. Для дерева используется элемент типа «Таблица формы», у которого свойство Отображение установлено в ОтображениеТаблицы.Дерево. Путь к данным элемента указывает на созданный реквизит.

	ЭлементДерево = Элементы.Добавить("ДеревоДанных", Тип("ТаблицаФормы"));

ЭлементДерево.ПутьКДанным = "ДеревоДанных";

ЭлементДерево.Отображение = ОтображениеТаблицы.Дерево;

КолонкаНаименование = Элементы.Добавить("ДеревоНаименование", Тип("ПолеФормы"), ЭлементДерево);

КолонкаНаименование.ПутьКДанным = "ДеревоДанных.Наименование";

КолонкаНаименование.Вид = ВидПоляФормы.ПолеВвода;

КолонкаКоличество = Элементы.Добавить("ДеревоКоличество", Тип("ПолеФормы"), ЭлементДерево);

КолонкаКоличество.ПутьКДанным = "ДеревоДанных.Количество";

КолонкаКоличество.Вид = ВидПоляФормы.ПолеВвода;

Колонки добавляются как дочерние элементы таблицы формы, а их ПутьКДанным строится как «ИмяДерева.ИмяКолонки». Если пропустить добавление колонок-элементов, таблица отобразится пустой, даже когда данные в реквизите есть — реквизит сам по себе ничего не рисует.

Полезные свойства таблицы-дерева: НачальноеОтображениеДерева (раскрывать или сворачивать узлы при открытии), Шапка, ПоложениеКоманднойПанели. Для больших деревьев имеет смысл установить начальное отображение «Не раскрывать», чтобы пользователь сам раскрывал нужные ветки.

Шаг 3. Заполняем дерево и переносим его в реквизит формы

Реквизит формы типа «ДеревоЗначений» на сервере представлен объектом специального типа — данными формы. Работать с ним как с обычным деревом (добавлять строки, обходить ветки) нельзя напрямую. Поэтому заполнение делается через промежуточный объект: либо создаётся новое ДеревоЗначений и переносится через ЗначениеВРеквизитФормы(), либо реквизит преобразуется в значение через РеквизитФормыВЗначение(), заполняется и возвращается обратно.

&НаСервере

Процедура ЗаполнитьДеревоНаСервере()

Запрос = Новый Запрос(

"ВЫБРАТЬ

| Товары.Номенклатура.Родитель КАК Группа,

| Товары.Номенклатура КАК Номенклатура,

| СУММА(Товары.Количество) КАК Количество

|ИЗ

| Документ.РеализацияТоваровУслуг.Товары КАК Товары

|ГДЕ

| Товары.Ссылка = &Ссылка

|СГРУППИРОВАТЬ ПО

| Товары.Номенклатура.Родитель,

| Товары.Номенклатура

|ИТОГИ ПО

| Группа");

Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);

ДеревоРезультат = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);

ЗначениеВРеквизитФормы(ДеревоРезультат, "ДеревоДанных");

КонецПроцедуры

Здесь есть важный нюанс: ЗначениеВРеквизитФормы() сопоставляет колонки по именам. Если в выгруженном из запроса дереве колонка называется «Группа», а в реквизите формы — «Наименование», данные не перенесутся. Приведите имена колонок к соответствию: либо переименуйте колонки дерева перед переносом, либо заполняйте строки вручную в цикле обхода.

Порядок действий
  1. 1
    В ПриСозданииНаСервере сформируйте массив реквизитов: само дерево и его колонки с типами
  2. 2
    Вызовите ИзменитьРеквизиты() с этим массивом
  3. 3
    Добавьте элемент ТаблицаФормы с Отображение = Дерево и поля-колонки с путями к данным
  4. 4
    Получите данные запросом с ИТОГИ ПО и выгрузите с ОбходРезультатаЗапроса.ПоГруппировкам
  5. 5
    Перенесите дерево в реквизит через ЗначениеВРеквизитФормы()
  6. 6
    Проверьте на форме: узлы раскрываются, значения колонок заполнены

Шаг 4. Ручное заполнение строк и работа с иерархией

Когда данные не укладываются в один запрос с итогами, дерево заполняется вручную. Получите объект дерева из реквизита, добавляйте строки на нужных уровнях через Строки.Добавить() и возвращайте результат обратно в реквизит.

&НаСервере

Процедура ЗаполнитьВручнуюНаСервере()

Дерево = РеквизитФормыВЗначение("ДеревоДанных");

Дерево.Строки.Очистить();

СтрокаГруппы = Дерево.Строки.Добавить();

СтрокаГруппы.Наименование = "Материалы";

СтрокаПозиции = СтрокаГруппы.Строки.Добавить();

СтрокаПозиции.Наименование = "Краска водно-дисперсионная";

СтрокаПозиции.Количество = 10;

ЗначениеВРеквизитФормы(Дерево, "ДеревоДанных");

КонецПроцедуры

Иерархия строится вложенностью: у каждой строки есть собственная коллекция Строки, и глубина вложенности не ограничена. Для поиска по дереву используйте Строки.Найти(Значение, "ИмяКолонки", Истина) — третий параметр включает поиск по всем уровням. Без него поиск идёт только в корневых строках, что частая причина «не находится» существующего элемента.

Если нужно перенести ветку из одного дерева в другое, используйте Скопировать() у строки или коллекции строк. Прямое присвоение строки другому родителю не работает — строка жёстко привязана к своему дереву.

Особенности работы на клиенте: идентификаторы строк

На клиенте дерево формы доступно как коллекция ДанныеФормыДерево, у которой нет методов Найти() с рекурсией и привычных операций серверного объекта. Зато у каждой строки есть метод ПолучитьИдентификатор() — числовой ID, по которому строку можно найти с любого уровня через ЭтаФорма.ДеревоДанных.НайтиПоИдентификатору(Ид).

Типовой сценарий: в обработчике ПриАктивизацииСтроки таблицы берётся Элементы.ДеревоДанных.ТекущаяСтрока — это и есть идентификатор, а не сама строка. Чтобы получить данные, вызовите ДеревоДанных.НайтиПоИдентификатору(ТекущаяСтрока) и читайте поля. Попытка обратиться к ТекущаяСтрока.Наименование напрямую даст ошибку, потому что это число.

Для программного разворачивания узлов на клиенте используйте Элементы.ДеревоДанных.Развернуть(ИдентификаторСтроки, Истина) — второй параметр разворачивает и все подчинённые ветки. Чтобы развернуть всё дерево, пройдите циклом по корневым строкам через ДеревоДанных.ПолучитьЭлементы() и разверните каждую.

Если дерево нужно только показать, а не редактировать, установите у таблицы формы свойство ТолькоПросмотр = Истина и отключите ИзменятьСоставСтрок. Это убережёт от случайного удаления строк пользователем и упростит командную панель.
ПЗ
Практическое замечание

Типичные ошибки и как их диагностировать

Первая и самая частая ошибка — «Метод объекта не обнаружен (Строки)» при обращении к реквизиту на сервере. Причина: попытка работать с данными формы как с объектом ДеревоЗначений. Лечится преобразованием через РеквизитФормыВЗначение() перед заполнением.

Вторая группа проблем — пустая таблица на форме при заполненном реквизите. Проверьте: совпадает ли ПутьКДанным элемента с именем реквизита; добавлены ли колонки-элементы; совпадают ли имена колонок источника и реквизита при ЗначениеВРеквизитФормы(). Ещё одна причина пустоты — вызов заполнения на клиенте, где серверный запрос недоступен: смотрите директивы компиляции &НаСервере.

Типичные ошибки
01
Вызов ИзменитьРеквизиты() вне ПриСозданииНаСервере — платформа выдаёт ошибку о недопустимом контексте, реквизит не создаётся
02
Несовпадение имён колонок запроса и реквизита формы — ЗначениеВРеквизитФормы() молча оставляет колонки пустыми
03
Поиск строки методом Найти() без третьего параметра Истина — поиск идёт только по корню, вложенные строки не находятся
04
Обращение к полям ТекущаяСтрока на клиенте — это идентификатор (число), а не строка данных
05
Забытые колонки-элементы у таблицы формы — данные в реквизите есть, но на форме ничего не видно

Проверка результата и ограничения подхода

После внедрения откройте форму и проверьте четыре вещи: дерево отображается с нужными колонками; узлы раскрываются и сворачиваются; итоги по группам соответствуют данным источника; при повторном открытии формы нет ошибок инициализации. Если форма открывается медленно, а строк в дереве тысячи — это сигнал пересмотреть подход.

Дерево значений целиком хранится в данных формы и передаётся между клиентом и сервером — на больших объёмах (десятки тысяч строк) это заметно тормозит открытие и каждый серверный вызов. Для иерархических справочников крупного размера лучше подходит динамический список с установленной иерархией: он подгружает данные порциями. Дерево значений оправдано для выборок ограниченного размера — строки документа по группам, структура заказа, настройки с вложенностью.

Контрольные точки

Как проверить результат

Отображение данных
Норма

Все колонки заполнены, узлы раскрываются, итоги групп совпадают с источником

Тревожный признак

Таблица пустая или часть колонок пуста при заполненном источнике

Повторное открытие формы
Норма

Форма открывается без ошибок, дерево заполняется заново

Тревожный признак

Ошибка при создании формы или дублирование реквизитов

Скорость работы
Норма

Открытие и серверные вызовы без заметных пауз

Тревожный признак

Форма «задумывается» на секунды — признак слишком большого дерева

Частые вопросы

Можно ли добавить колонку в уже созданный реквизит дерева?

Нет, состав колонок фиксируется в момент вызова ИзменитьРеквизиты(). Чтобы изменить структуру, нужно удалить реквизит и создать заново, что возможно только при повторном создании формы. Если состав колонок может меняться в процессе работы, предусмотрите максимальный набор колонок заранее и управляйте их видимостью у элементов формы.

Как вывести дерево на обычную (не управляемую) форму?

В обычных формах всё проще: добавьте реквизит формы типа ДеревоЗначений в конфигураторе или кодом, поместите на форму элемент «ТабличноеПоле» с типом значения «ДеревоЗначений» и присвойте ему данные напрямую через ЭлементыФормы.ТабличноеПоле1.Значение = Дерево. Преобразования РеквизитФормыВЗначение там не нужны.

Почему ЗначениеВРеквизитФормы() не переносит данные, хотя дерево заполнено?

Чаще всего не совпадают имена колонок источника и реквизита формы — сопоставление идёт строго по именам. Также проверьте совместимость типов: строка длиной 150 в реквизите и несовместимый тип в источнике могут приводить к потере значений. Выведите структуру дерева в отладчике и сравните с описанием реквизита.

Как сохранить состояние развёрнутых узлов при обновлении дерева?

Перед перезаполнением пройдите по строкам и запомните ключи развёрнутых веток (например, значения ключевой колонки). После заполнения найдите соответствующие строки поиском с рекурсией и вызовите Развернуть() для их идентификаторов. Автоматического сохранения состояния у дерева значений нет.

Можно ли редактировать дерево прямо на форме и получить изменения на сервере?

Да. Пользователь редактирует данные формы напрямую, изменения доступны в реквизите сразу. На сервере получите объект через РеквизитФормыВЗначение("ДеревоДанных") и обработайте строки обычными методами ДереваЗначений. Для контроля ввода используйте события таблицы формы: ПриИзменении, ПередУдалением, ПередНачаломДобавления.

Что выбрать для иерархического справочника на форме: дерево значений или динамический список?

Для справочника с произвольным количеством элементов берите динамический список с иерархией — он не грузит всё в память формы. Дерево значений подходит для ограниченных выборок: состав документа по группам, структура заказа, дерево настроек, где строк относительно немного и нужна свободная программная манипуляция данными.

Итог

Что важно запомнить

Дерево выводится на управляемую форму в три шага: создание реквизита через ИзменитьРеквизиты() в ПриСозданииНаСервере, добавление таблицы формы с отображением «Дерево», перенос данных через ЗначениеВРеквизитФормы(). Следите за совпадением имён колонок, преобразовывайте реквизит в значение перед заполнением и не используйте дерево значений для очень больших иерархий.