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

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

Как создать дерево значений в 1С: пошаговая инструкция

Как создать дерево значений в 1С: добавление колонок и строк, обход дерева, поиск, вывод в табличное поле и отличия от таблицы значений. Примеры кода.

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

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

Материал рассчитан на разработчиков и продвинутых пользователей, работающих с конфигуратором. Все примеры приведены на встроенном языке 1С и подходят для платформы 8.3, как для управляемых, так и для обычных форм — с учётом различий, о которых сказано отдельно.

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

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

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

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

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

Дерево.Колонки.Добавить("Ссылка");

Если тип не указан, колонка принимает значения произвольного типа — это удобно для ссылок на разные виды объектов, но замедляет сортировку и может привести к ошибкам сравнения. Для колонок, участвующих в итогах и поиске, тип лучше задавать явно через ОписаниеТипов.

Заполнение дерева строками

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

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

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

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

Элемент.Наименование = "Стальной лист 2 мм";

Элемент.Количество = 10;

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

Запрос = Новый Запрос("ВЫБРАТЬ Номенклатура.Ссылка КАК Ссылка ИЗ Справочник.Номенклатура КАК Номенклатура ИТОГИ ПО Ссылка ТОЛЬКО ИЕРАРХИЯ");

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

Порядок действий при ручном заполнении
  1. 1
    Создайте объект: Дерево = Новый ДеревоЗначений
  2. 2
    Добавьте колонки с типами через Дерево.Колонки.Добавить()
  3. 3
    Добавьте строки верхнего уровня через Дерево.Строки.Добавить()
  4. 4
    У каждой строки-родителя вызовите Строки.Добавить() для дочерних элементов
  5. 5
    Проверьте результат выводом в сообщение или табличное поле

Обход дерева и поиск строк

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

Процедура ОбойтиДерево(Строки)

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

Сообщить(Строка.Наименование);

ОбойтиДерево(Строка.Строки);

КонецЦикла;

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

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

bsl
Функция НайтиСтрокуРекурсивно(Строки, ИскомоеЗначение)
    Для Каждого Строка Из Строки Цикл
        Если Строка.Ссылка = ИскомоеЗначение Тогда
            Возврат Строка;
        КонецЕсли;
        Найденная = НайтиСтрокуРекурсивно(Строка.Строки, ИскомоеЗначение);
        Если Найденная <> Неопределено Тогда
            Возврат Найденная;
        КонецЕсли;
    КонецЦикла;
    Возврат Неопределено;
КонецФункции

Вывод дерева на форму

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

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

Дерево значений против таблицы значений

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

Сравнение вариантов

Таблица значений

Подходит для плоских списков: строк документа, результатов запроса без иерархии. Поддерживает индексы, Свернуть(), НайтиСтроки(). Не хранит вложенность.

Дерево значений

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

Запрос с итогами по иерархии

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

Типичные ошибки и ограничения

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

Типичные ошибки
01
Обход только верхнего уровня без рекурсии — подчинённые строки остаются необработанными
02
Передача дерева значений с клиента на сервер в управляемой форме — ошибка сериализации, используйте реквизит формы
03
Добавление строк в Дерево.Строки вместо СтрокиРодителя.Строки — вся иерархия вырождается в плоский список
04
Колонки без явного типа — ошибки сравнения и сортировки при смешанных данных

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

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

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

Структура дерева
Норма

У строк верхнего уровня заполнены подчинённые коллекции Строки там, где ожидается вложенность

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

Все строки оказались на одном уровне — добавление шло не в ту коллекцию

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

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

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

Показан плоский список или пустая форма — проверьте тип реквизита и вызов ЗначениеВРеквизитФормы

Полнота обхода
Норма

Рекурсивная процедура обработала все уровни, счётчик совпадает с ожидаемым числом строк

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

Обработана только часть строк — в обходе пропущен рекурсивный вызов

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

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

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

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

Напрямую нет — объект существует только в памяти сеанса. Для хранения дерево сериализуют через ЗначениеВСтрокуВнутр() или помещают в хранилище значения реквизита объекта. Чаще иерархию хранят естественным образом — в иерархическом справочнике.

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

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

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

В управляемом приложении дерево значений не сериализуется при клиент-серверных вызовах. Решение — хранить дерево в реквизите формы типа ДеревоЗначений и работать с ним через ЗначениеВРеквизитФормы и РеквизитФормыВЗначение.

Как найти строку на любом уровне дерева?

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

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

Метода Свернуть() у дерева нет. Итоги рассчитывают в рекурсивном обходе: функция возвращает сумму по дочерним строкам, а родительская строка аккумулирует результаты. Альтернатива — получить итоги сразу в запросе с группировками по иерархии.

Итог

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

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