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

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

Как программно заполнить дерево значений в 1С

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

Дерево значений в 1С — это иерархическая структура данных, где у каждой строки могут быть подчинённые строки. Оно применяется для отображения номенклатуры по группам, структуры организации, иерархических справочников, многоуровневых итогов и любых данных с отношением «родитель — потомок». В отличие от таблицы значений, дерево нельзя заполнить прямым запросом «в лоб» без дополнительной обработки иерархии, поэтому у начинающих разработчиков заполнение дерева вызывает больше всего вопросов.

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

Создание дерева значений и описание колонок

Дерево значений создаётся конструктором Новый ДеревоЗначений. Сразу после создания у него нет ни одной колонки — их нужно добавить явно, указав имя и тип. Тип задаётся через объект ОписаниеТипов, что позволяет ограничить состав типов и длину строк.

Дерево = Новый ДеревоЗначений;

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

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

ТипСсылка = Новый ОписаниеТипов("СправочникСсылка.Номенклатура");

Дерево.Колонки.Добавить("Номенклатура", ТипСсылка);

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

Дерево.Колонки.Добавить("Количество", ТипЧисло);

Дерево.Колонки.Добавить("Сумма", ТипЧисло);

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

У дерева значений есть служебная особенность: у каждой строки есть свойство Родитель и коллекция Строки для подчинённых строк. Именно через них строится иерархия — отдельного «уровня» или «кода родителя» в самой структуре нет.

Добавление строк и построение иерархии вручную

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

// Корневой уровень — группа

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

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

// Подчинённая строка

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

СтрокаТовар.Наименование = "Дрель ударная";

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

СтрокаТовар.Сумма = 45000;

// Ещё одна строка в ту же группу

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

СтрокаТовар2.Наименование = "Шуруповёрт";

СтрокаТовар2.Количество = 5;

СтрокаТовар2.Сумма = 32000;

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

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

Заполнение дерева из запроса с итогами по иерархии

Самый распространённый сценарий — заполнить дерево результатом запроса. Для этого в тексте запроса используется конструкция ИТОГИ ПО, а результат обходится методом Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам). Каждый уровень группировки превращается в уровень дерева.

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

Запрос.Текст =

"ВЫБРАТЬ

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

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

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

|ИЗ

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

|ГДЕ

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

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

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

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

|ИТОГИ ПО

| Группа";

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

Результат = Запрос.Выполнить();

Дерево = Новый ДеревоЗначений;

Дерево.Колонки.Добавить("Группа");

Дерево.Колонки.Добавить("Номенклатура");

Дерево.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));

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

Пока ВыборкаГруппы.Следующий() Цикл

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

СтрокаГруппа.Группа = ВыборкаГруппы.Группа;

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

Пока ВыборкаДетали.Следующий() Цикл

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

Строка.Номенклатура = ВыборкаДетали.Номенклатура;

Строка.Количество = ВыборкаДетали.Количество;

КонецЦикла;

КонецЦикла;

Обратите внимание: детальные записи внутри группировки получают через ВыборкаГруппы.Выбрать() без параметра — это даёт линейную выборку по детальным записям текущей группы. Вложенность группировок может быть любой, тогда и циклы вкладываются друг в друга на каждый уровень.

Заполнение дерева по иерархическому справочнику

Когда источником служит иерархический справочник (например, «Номенклатура» с группами), удобнее использовать запрос с обходом по иерархии через ИТОГИ ПО ... ТОЛЬКО ИЕРАРХИЯ либо рекурсивную процедуру обхода. Рекурсивный вариант нагляднее и проще отлаживается.

Процедура ЗаполнитьВетку(РодительСсылка, СтрокиДерева)

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

Запрос.Текст =

"ВЫБРАТЬ

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

| Номенклатура.ЭтоГруппа КАК ЭтоГруппа

|ИЗ

| Справочник.Номенклатура КАК Номенклатура

|ГДЕ

| Номенклатура.Родитель = &Родитель

| И НЕ Номенклатура.ПометкаУдаления

|

|УПОРЯДОЧИТЬ ПО

| Номенклатура.Наименование";

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

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл

НоваяСтрока = СтрокиДерева.Добавить();

НоваяСтрока.Номенклатура = Выборка.Ссылка;

Если Выборка.ЭтоГруппа Тогда

ЗаполнитьВетку(Выборка.Ссылка, НоваяСтрока.Строки);

КонецЕсли;

КонецЦикла;

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

// Вызов для корня:

ЗаполнитьВетку(Справочники.Номенклатура.ПустаяСсылка(), Дерево.Строки);

Недостаток рекурсивного подхода — отдельный запрос на каждый узел, что при большом количестве групп создаёт заметную нагрузку на СУБД. Для крупных справочников лучше один раз выбрать всю таблицу с полем «Родитель» и строить дерево в памяти, находя родителя через соответствие.

Способы заполнения дерева

ИТОГИ ПО + обход по группировкам

Быстро и лаконично, но число уровней фиксировано структурой запроса. Подходит для отчётов с известной глубиной.

Рекурсивный обход справочника

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

Один запрос + соответствие родителей

Оптимально по скорости: одна выборка, дерево собирается в памяти через Соответствие «ссылка → строка дерева». Требует аккуратного кода.

Вывод дерева значений на форму

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

&НаСервере

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

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

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

// ... заполнение дерева любым из способов выше

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

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

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

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

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

Проверка результата и типичные ошибки

После заполнения полезно убедиться, что иерархия построена правильно. Самый быстрый способ — обойти дерево рекурсивно и вывести структуру в окно сообщений с отступом по уровню. Это позволяет увидеть «потерянные» ветки и строки, попавшие не на тот уровень.

Процедура ПроверитьДерево(Строки, Уровень = 0)

Для Каждого Строка Из Строки Цикл

Отступ = "";

Для Н = 1 По Уровень Цикл

Отступ = Отступ + " ";

КонецЦикла;

Сообщить(Отступ + Строка.Наименование);

ПроверитьДерево(Строка.Строки, Уровень + 1);

КонецЦикла;

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

Также проверьте итоги: метод Строки.Итог("Сумма", Истина) с параметром Истина суммирует значения по всем уровням иерархии. Если итог не совпадает с ожидаемым, вероятно, часть строк попала не в ту ветку или дублируется.

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

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

Структура иерархии
Норма

Рекурсивный обход показывает ветки с правильной вложенностью

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

Все строки на одном уровне или часть веток отсутствует

Итоги по числовым колонкам
Норма

Строки.Итог() с Истина совпадает с итогом исходных данных

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

Сумма больше или меньше — есть дубли или потерянные строки

Отображение на форме
Норма

Дерево показывает треугольники раскрытия и нужные колонки

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

Плоская таблица без иерархии или пустые колонки

Ограничения и рекомендации по производительности

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

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

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

Часто задаваемые вопросы

Чем дерево значений отличается от таблицы значений?

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

Как скопировать одну ветку дерева в другое дерево?

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

Можно ли заполнить дерево значений без запроса, из таблицы значений?

Да. Если в таблице значений есть колонка со ссылкой на родителя, создайте Соответствие «родитель → строка дерева», пройдите по таблице циклом и добавляйте каждую строку либо в корень, либо в коллекцию Строки найденного родителя. Это самый быстрый способ для больших объёмов.

Почему на форме дерево отображается как плоская таблица?

Чаще всего причина в том, что все строки добавлены на верхний уровень — проверьте, что дочерние строки добавляются в Строки родительской строки, а не в Дерево.Строки. Вторая возможная причина — реквизит формы создан как ТаблицаЗначений, а не ДеревоЗначений.

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

Используйте метод Удалить() у коллекции, содержащей строку: РодительскаяСтрока.Строки.Удалить(СтрокаДляУдаления). Все подчинённые строки удалятся автоматически вместе с родителем, отдельно обходить ветку не нужно.

Как отсортировать дерево с учётом иерархии?

Метод Строки.Сортировать("Наименование", Истина) сортирует строки внутри каждого уровня: второй параметр Истина включает рекурсивную сортировку всех подчинённых коллекций. Структура ветвей при этом сохраняется — меняется только порядок строк внутри каждого родителя.

Итог

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

Дерево значений заполняется через добавление колонок и строк с явным указанием родительской ветки. Для иерархических данных используйте ИТОГИ ПО с обходом по группировкам либо один запрос и сборку через Соответствие. На форму дерево выводится через реквизит типа ДеревоЗначений с преобразованием РеквизитФормыВЗначение и обратно. Перед повторным заполнением очищайте строки, а результат проверяйте рекурсивным обходом и сверкой итогов.