Как сохранить дерево значений в табличную часть документа и восстановить его при открытии

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

При разработке интерфейсов в 1С часто возникает задача отобразить данные в виде иерархического дерева (ДеревоЗначений или элемент формы с типом ДанныеФормыДерево). Однако в структуре метаданных 1С нельзя создать реквизит документа или колонку табличной части с типом «Дерево значений» — база данных 1С является плоской и реляционной. Поэтому при стандартной записи документа данные дерева на форме автоматически в базу не сохраняются.

Разберем основные подходы, которые позволяют правильно перенести иерархию дерева в табличную часть документа при сохранении и восстановить визуальное дерево при повторном открытии формы.

Способ 1. Сохранение иерархии в табличную часть через уникальные идентификаторы

Этот способ является наиболее надежным и универсальным. Он позволяет сохранить произвольный уровень вложенности строк и не зависит от сортировки или перемещения строк в табличной части.

Рассмотрим структуру метаданных. В табличную часть документа (например, Товары) помимо прикладных реквизитов добавим два строковых реквизита:

  1. ИдентификаторСтроки (тип Строка, длина 36) — уникальный GUID текущей строки.
  2. ИдентификаторРодителя (тип Строка, длина 36) — GUID родительской строки (для строк верхнего уровня остается пустой строкой).

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


Процедура ВыгрузитьДеревоВТабличнуюЧасть(СтрокиДерева, ИдентификаторРодителя = "")
    Для Каждого СтрДерева Из СтрокиДерева Цикл
        ИдентификаторСтроки = Строка(Новый УникальныйИдентификатор());
        
        НоваяСтрокаТЧ = Объект.Товары.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрокаТЧ, СтрДерева);
        НоваяСтрокаТЧ.ИдентификаторСтроки = ИдентификаторСтроки;
        НоваяСтрокаТЧ.ИдентификаторРодителя = ИдентификаторРодителя;
        
        // Рекурсивно обходим дочерние строки
        Если СтрДерева.Строки.Количество() > 0 Тогда
            ВыгрузитьДеревоВТабличнуюЧасть(СтрДерева.Строки, ИдентификаторСтроки);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
    Объект.Товары.Очистить();
    ВыгрузитьДеревоВТабличнуюЧасть(ДеревоНаФорме.Строки);
КонецПроцедуры

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


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

Способ 2. Сохранение иерархии по номерам строк или уровню вложенности

Если нет возможности добавлять строковые GUID-поля, сохранение связей можно организовать через числовой реквизит Уровень или НомерСтрокиРодителя. Рассмотрим вариант со стековым алгоритмом по уровню вложенности:

  1. В табличную часть добавляется числовой реквизит УровеньИерархии.
  2. При прямом обходе дерева строк на каждом шаге в реквизит УровеньИерархии записывается глубина (0 для корня, 1 для первого уровня и т.д.).
  3. При восстановлении используется вспомогательный массив, где индекс соответствует уровню родителя.

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

Способ 3. Использование реквизита типа «ХранилищеЗначения»

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

Посмотрим на пример кода сохранения и чтения:


// Сохранение перед записью объекта
ДеревоДляЗаписи = РеквизитФормыВЗначение("ДеревоНаФорме");
Объект.ДеревоДанных = Новый ХранилищеЗначения(ДеревоДляЗаписи, Новый СжатиеДанных(9));

// Чтение и восстановление при открытии
ДеревоИзХранилища = Объект.ДеревоДанных.Получить();
Если ТипЗнч(ДеревоИзХранилища) = Тип("ДеревоЗначений") Тогда
    ЗначениеВРеквизитФормы(ДеревоИзХранилища, "ДеревоНаФорме");
КонецЕсли;

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

Типичные ошибки при реализации

  1. Попытка вызова сохранения в обработчике «ПриЗаписи»: событие формы ПриЗаписи выполняется уже после того, как данные документа зафиксированы в базе данных. Выгрузку дерева в табличную часть необходимо выполнять строго в событии ПередЗаписью (на форме или в модуле объекта).
  2. Отсутствие очистки табличной части: если перед обходом дерева не вызывать Объект.Товары.Очистить(), при каждом нажатии кнопки сохранения строки в документе будут дублироваться.
  3. Ожидание автоматической загрузки: платформа 1С не связывает напрямую реквизит формы типа дерево с табличной частью объекта. После записи документа алгоритм обратной загрузки данных из ТЧ в дерево формы должен быть явно вызван разработчиком при чтении объекта.
← На главную