При разработке интерфейсов в 1С часто возникает задача отобразить данные в виде иерархического дерева (ДеревоЗначений или элемент формы с типом ДанныеФормыДерево). Однако в структуре метаданных 1С нельзя создать реквизит документа или колонку табличной части с типом «Дерево значений» — база данных 1С является плоской и реляционной. Поэтому при стандартной записи документа данные дерева на форме автоматически в базу не сохраняются.
Разберем основные подходы, которые позволяют правильно перенести иерархию дерева в табличную часть документа при сохранении и восстановить визуальное дерево при повторном открытии формы.
Этот способ является наиболее надежным и универсальным. Он позволяет сохранить произвольный уровень вложенности строк и не зависит от сортировки или перемещения строк в табличной части.
Рассмотрим структуру метаданных. В табличную часть документа (например, Товары) помимо прикладных реквизитов добавим два строковых реквизита:
Строка, длина 36) — уникальный GUID текущей строки.Строка, длина 36) — GUID родительской строки (для строк верхнего уровня остается пустой строкой).Разберем алгоритм выгрузки дерева в табличную часть перед записью документа. Перед обходом обязательно очищаем табличную часть, чтобы избежать дублирования строк при повторных сохранениях:
Процедура ВыгрузитьДеревоВТабличнуюЧасть(СтрокиДерева, ИдентификаторРодителя = "")
Для Каждого СтрДерева Из СтрокиДерева Цикл
ИдентификаторСтроки = Строка(Новый УникальныйИдентификатор());
НоваяСтрокаТЧ = Объект.Товары.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрокаТЧ, СтрДерева);
НоваяСтрокаТЧ.ИдентификаторСтроки = ИдентификаторСтроки;
НоваяСтрокаТЧ.ИдентификаторРодителя = ИдентификаторРодителя;
// Рекурсивно обходим дочерние строки
Если СтрДерева.Строки.Количество() > 0 Тогда
ВыгрузитьДеревоВТабличнуюЧасть(СтрДерева.Строки, ИдентификаторСтроки);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Процедура ПередЗаписью(Отказ, ПараметрыЗаписи)
Объект.Товары.Очистить();
ВыгрузитьДеревоВТабличнуюЧасть(ДеревоНаФорме.Строки);
КонецПроцедуры
Теперь проанализируем, как быстро восстановить дерево из табличной части при открытии формы (например, в событии ПриЧтенииНаСервере или ПриСозданииНаСервере). Для этого не требуется рекурсия — достаточно использовать объект Соответствие для кэширования созданных строк дерева:
Процедура ЗаполнитьДеревоИзТабличнойЧасти()
ДеревоНаФорме.Строки.Очистить();
КартаРодителей = Новый Соответствие;
Для Каждого СтрТЧ Из Объект.Товары Цикл
Если ПустаяСтрока(СтрТЧ.ИдентификаторРодителя) Тогда
НоваяСтрДерева = ДеревоНаФорме.Строки.Добавить();
Иначе
РодительскаяСтрока = КартаРодителей[СтрТЧ.ИдентификаторРодителя];
Если РодительскаяСтрока <> Неопределено Тогда
НоваяСтрДерева = РодительскаяСтрока.Строки.Добавить();
Иначе
НоваяСтрДерева = ДеревоНаФорме.Строки.Добавить();
КонецЕсли;
КонецЕсли;
ЗаполнитьЗначенияСвойств(НоваяСтрДерева, СтрТЧ);
КартаРодителей.Вставить(СтрТЧ.ИдентификаторСтроки, НоваяСтрДерева);
КонецЦикла;
КонецПроцедуры
Если нет возможности добавлять строковые GUID-поля, сохранение связей можно организовать через числовой реквизит Уровень или НомерСтрокиРодителя. Рассмотрим вариант со стековым алгоритмом по уровню вложенности:
УровеньИерархии.УровеньИерархии записывается глубина (0 для корня, 1 для первого уровня и т.д.).Важно: такой подход требует строгого сохранения порядка строк в табличной части. Если пользователь отсортирует строки в документе вручную, иерархия нарушится.
Если данные дерева не используются для проведения документа по регистрам, не участвуют в движениях и по ним не нужно строить отчеты через язык запросов 1С, дерево можно сериализовать целиком в бинарный реквизит документа с типом ХранилищеЗначения.
Посмотрим на пример кода сохранения и чтения:
// Сохранение перед записью объекта
ДеревоДляЗаписи = РеквизитФормыВЗначение("ДеревоНаФорме");
Объект.ДеревоДанных = Новый ХранилищеЗначения(ДеревоДляЗаписи, Новый СжатиеДанных(9));
// Чтение и восстановление при открытии
ДеревоИзХранилища = Объект.ДеревоДанных.Получить();
Если ТипЗнч(ДеревоИзХранилища) = Тип("ДеревоЗначений") Тогда
ЗначениеВРеквизитФормы(ДеревоИзХранилища, "ДеревоНаФорме");
КонецЕсли;
Главный недостаток этого метода — невозможность обращения к элементам дерева через язык запросов и отсутствие контроля ссылочной целостности объектов, находящихся внутри хранилища.
ПриЗаписи выполняется уже после того, как данные документа зафиксированы в базе данных. Выгрузку дерева в табличную часть необходимо выполнять строго в событии ПередЗаписью (на форме или в модуле объекта).Объект.Товары.Очистить(), при каждом нажатии кнопки сохранения строки в документе будут дублироваться.