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