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