При разработке решений на платформе 1С:Предприятие 8.3 с использованием управляемых форм часто возникает необходимость отображать древовидные данные. Стандартный объект для хранения таких данных в коде — это ДеревоЗначений. Однако для отображения на управляемой форме эти данные необходимо перенести в реквизит формы типа ДанныеФормыДерево. Эта задача может быть неочевидной, особенно когда структура (количество и имена колонок) исходного ДереваЗначений не полностью совпадает со структурой ДанныхФормыДерево.
Рассмотрим ситуацию, когда у нас есть ДеревоЗначений, полученное, например, из запроса или сформированное программно, и нам нужно отобразить его на форме. На форме уже определен реквизит типа ДанныеФормыДерево с определенным набором колонок. Возникает вопрос: как наиболее эффективно и корректно перенести данные, учитывая, что колонки могут не совпадать по количеству или даже по именам, если это требуется для логической связи?
Для решения этой задачи существуют два основных подхода: использование стандартной функции из Библиотеки стандартных подсистем (БСП) или написание собственной рекурсивной процедуры для более тонкой настройки переноса.
Платформа 1С:Предприятие и БСП предоставляют мощные инструменты для решения типовых задач. Одной из таких задач является копирование древовидных структур данных. Функция ОбщегоНазначения.ЗаполнитьКоллекциюЭлементовДереваДанныхФормы специально предназначена для заполнения реквизита формы типа ДанныеФормыДерево данными из объекта ДеревоЗначений.
Эта процедура выполняет рекурсивный обход всех строк исходного ДереваЗначений, включая все вложенные строки. Для каждой строки исходного дерева она создает соответствующий новый элемент в целевом ДанныеФормыДерево. Ключевым моментом здесь является то, как происходит перенос значений свойств (колонок) между строками. Функция использует метод ЗаполнитьЗначенияСвойств.
Рассмотрим подробнее поведение при различных структурах колонок:
ДеревеЗначений и в целевом ДанныхФормыДерево совпадают, и типы данных этих колонок совместимы, то значения будут автоматически скопированы. Это самый простой и эффективный сценарий.ЗаполнитьЗначенияСвойств копирует только те свойства, имена которых совпадают в источнике и приемнике. Если в ДеревеЗначений есть колонка с именем, которое отсутствует в ДанныхФормыДерево, значение этой колонки не будет перенесено. Аналогично, если в ДанныхФормыДерево есть колонка, для которой нет совпадающей по имени колонки в ДеревеЗначений, эта колонка останется незаполненной (примет значение по умолчанию или Неопределено).Предположим, у нас есть `ДеревоЗначений` со структурой:
Наименование (Строка)Количество (Число)Цена (Число)Идентификатор (Строка)И на форме определен реквизит `ДеревоНаФорме` типа `ДанныеФормыДерево` со следующими колонками:
Наименование (Строка)Количество (Число)Сумма (Число)СсылкаНаОбъект (Строка)Как мы видим, колонки Наименование и Количество совпадают, а Цена/Сумма и Идентификатор/СсылкаНаОбъект – нет. В этом случае функция `ЗаполнитьКоллекциюЭлементовДереваДанныхФормы` перенесет только Наименование и Количество.
// В модуле формы
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// 1. Создаем исходное ДеревоЗначений
ПеременнаяДеревоЗначений = Новый ДеревоЗначений;
// Добавляем колонки в ДеревоЗначений
ПеременнаяДеревоЗначений.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка"));
ПеременнаяДеревоЗначений.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
ПеременнаяДеревоЗначений.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число"));
ПеременнаяДеревоЗначений.Колонки.Добавить("Идентификатор", Новый ОписаниеТипов("Строка"));
// Заполняем ДеревоЗначений тестовыми данными
НоваяСтрока = ПеременнаяДеревоЗначений.Добавить();
НоваяСтрока.Наименование = "Группа товаров 1";
НоваяСтрока.Количество = 0; // Для групп может быть 0
НоваяСтрока.Цена = 0;
НоваяСтрока.Идентификатор = УникальныйИдентификатор();
СтрокаДочерняя = НоваяСтрока.Строки.Добавить();
СтрокаДочерняя.Наименование = "Товар 1.1";
СтрокаДочерняя.Количество = 10;
СтрокаДочерняя.Цена = 100;
СтрокаДочерняя.Идентификатор = УникальныйИдентификатор();
СтрокаДочерняя2 = НоваяСтрока.Строки.Добавить();
СтрокаДочерняя2.Наименование = "Товар 1.2";
СтрокаДочерняя2.Количество = 25;
СтрокаДочерняя2.Цена = 50;
СтрокаДочерняя2.Идентификатор = УникальныйИдентификатор();
НоваяСтрока2 = ПеременнаяДеревоЗначений.Добавить();
НоваяСтрока2.Наименование = "Группа товаров 2";
НоваяСтрока2.Количество = 0;
НоваяСтрова2.Цена = 0;
НоваяСтрока2.Идентификатор = УникальныйИдентификатор();
СтрокаДочерняя3 = НоваяСтрока2.Строки.Добавить();
СтрокаДочерняя3.Наименование = "Товар 2.1";
СтрокаДочерняя3.Количество = 5;
СтрокаДочерняя3.Цена = 200;
СтрокаДочерняя3.Идентификатор = УникальныйИдентификатор();
// 2. Инициализируем реквизит формы типа ДанныеФормыДерево
// Предполагается, что на форме уже есть реквизит с именем "ДеревоНаФорме"
// и колонки для него заданы в конфигураторе или программно, например:
// Наименование, Количество, Сумма, СсылкаНаОбъект
// Очищаем текущие данные в ДеревеНаФорме перед заполнением
ДеревоНаФорме.Очистить();
// 3. Вызываем функцию БСП для заполнения
// Первый параметр: исходное ДеревоЗначений
// Второй параметр: целевой реквизит формы типа ДанныеФормыДерево
ОбщегоНазначения.ЗаполнитьКоллекциюЭлементовДереваДанныхФормы(ПеременнаяДеревоЗначений, ДеревоНаФорме);
// После выполнения этой функции:
// - Колонки "Наименование" и "Количество" в ДеревеНаФорме будут заполнены
// - Колонки "Сумма" и "СсылкаНаОбъект" останутся незаполненными (Неопределено или 0)
// так как в ПеременнаяДеревоЗначений нет колонок с такими же именами.
КонецПроцедуры
Важный момент: для того чтобы функция ЗаполнитьКоллекциюЭлементовДереваДанныхФормы могла работать, реквизит формы ДеревоНаФорме должен быть уже создан на форме и иметь необходимые колонки. Колонки могут быть определены в конфигураторе для реквизита формы или программно при создании формы.
Этот способ является предпочтительным, если большинство колонок имеют совпадающие имена или если для несовпадающих колонок не требуется сложная логика заполнения. Он прост в использовании и позволяет избежать ручной реализации рекурсивного обхода.
В случаях, когда требуется более тонкая настройка переноса данных – например, если имена колонок не совпадают, но логически они должны быть связаны (например, "Номенклатура" в источнике и "Товар" в приемнике), или если нужно выполнить какие-то преобразования данных перед переносом, – мы можем реализовать собственный рекурсивный обход ДереваЗначений.
При таком подходе мы явно указываем, из какой колонки ДереваЗначений в какую колонку ДанныхФормыДерево переносить данные, используя прямой доступ к свойствам строк. Этот метод дает нам полный контроль над процессом.
Вернемся к нашему примеру с несовпадающими колонками:
ДеревоЗначений имеет колонки: Наименование, Количество, Цена, Идентификатор.ДанныеФормыДерево имеет колонки: Наименование, Количество, Сумма, СсылкаНаОбъект.Мы хотим, чтобы:
Наименование из источника перешло в Наименование приемника.Количество из источника перешло в Количество приемника.Цена из источника умноженная на Количество перешла в Сумма приемника.Идентификатор из источника перешло в СсылкаНаОбъект приемника.Для этого мы напишем две вспомогательные процедуры: одну, которая запускает процесс, и вторую, рекурсивную, которая будет обходить дерево и переносить данные.
// В модуле формы
&НаСервере
Процедура ЗаполнитьДеревоФормыСПересчетомНаСервере()
// 1. Создаем исходное ДеревоЗначений (как в предыдущем примере)
ПеременнаяДеревоЗначений = Новый ДеревоЗначений;
ПеременнаяДеревоЗначений.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка"));
ПеременнаяДеревоЗначений.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
ПеременнаяДеревоЗначений.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число"));
ПеременнаяДеревоЗначений.Колонки.Добавить("Идентификатор", Новый ОписаниеТипов("Строка"));
НоваяСтрока = ПеременнаяДеревоЗначений.Добавить();
НоваяСтрока.Наименование = "Группа товаров 1";
НоваяСтрока.Количество = 0;
НоваяСтрока.Цена = 0;
НоваяСтрока.Идентификатор = "ГР00000001";
СтрокаДочерняя = НоваяСтрова.Строки.Добавить();
СтрокаДочерняя.Наименование = "Ноутбук ASUS";
СтрокаДочерняя.Количество = 3;
СтрокаДочерняя.Цена = 50000;
СтрокаДочерняя.Идентификатор = "Т00000001";
СтрокаДочерняя2 = НоваяСтрова.Строки.Добавить();
СтрокаДочерняя2.Наименование = "Мышь Logitech";
СтрокаДочерняя2.Количество = 10;
СтрокаДочерняя2.Цена = 1500;
СтрокаДочерняя2.Идентификатор = "Т00000002";
НоваяСтрока2 = ПеременнаяДеревоЗначений.Добавить();
НоваяСтрока2.Наименование = "Группа услуг 2";
НоваяСтрока2.Количество = 0;
НоваяСтрока2.Цена = 0;
НоваяСтрока2.Идентификатор = "ГР00000002";
СтрокаДочерняя3 = НоваяСтрока2.Строки.Добавить();
СтрокаДочерняя3.Наименование = "Настройка ПО";
СтрокаДочерняя3.Количество = 2;
СтрокаДочерняя3.Цена = 3000;
СтрокаДочерняя3.Идентификатор = "У00000001";
// 2. Инициализируем реквизит формы типа ДанныеФормыДерево
// Предполагается, что на форме уже есть реквизит с именем "ДеревоНаФорме"
// и колонки для него заданы в конфигураторе: Наименование, Количество, Сумма, СсылкаНаОбъект
// Очищаем текущие данные в ДеревеНаФорме перед заполнением
ДеревоНаФорме.Очистить();
// 3. Запускаем рекурсивное заполнение
ЗаполнитьСтрокиДереваНаФорме(ПеременнаяДеревоЗначений.Строки, ДеревоНаФорме.Строки);
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьСтрокиДереваНаФорме(ИсходныеСтроки, ЦелевыеСтроки)
Для Каждого ИсходнаяСтрокаДерева Из ИсходныеСтроки Цикл
НоваяСтрокаНаФорме = ЦелевыеСтроки.Добавить();
// Явное сопоставление и перенос данных
НоваяСтрокаНаФорме.Наименование = ИсходнаяСтрокаДерева.Наименование;
НоваяСтрокаНаФорме.Количество = ИсходнаяСтрокаДерева.Количество;
// Преобразование: Цена * Количество -> Сумма
Если ИсходнаяСтрокаДерева.Количество > 0 И ИсходнаяСтрокаДерева.Цена > 0 Тогда
НоваяСтрокаНаФорме.Сумма = ИсходнаяСтрокаДерева.Количество * ИсходнаяСтрокаДерева.Цена;
Иначе
НоваяСтрокаНаФорме.Сумма = 0;
КонецЕсли;
// Переименование колонки: Идентификатор -> СсылкаНаОбъект
НоваяСтрокаНаФорме.СсылкаНаОбъект = ИсходнаяСтрокаДерева.Идентификатор;
// Рекурсивный вызов для дочерних строк
Если ИсходнаяСтрокаДерева.Строки.Количество() > 0 Тогда
ЗаполнитьСтрокиДереваНаФорме(ИсходнаяСтрокаДерева.Строки, НоваяСтрокаНаФорме.Строки);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
В этом примере мы видим, как достигается полный контроль:
Этот подход предоставляет максимальную гибкость и незаменим, когда требуется сложная логика маппинга или преобразования данных.
Теперь, когда мы рассмотрели оба метода, давайте выясним, какой из них выбрать в конкретных ситуациях:
ОбщегоНазначения.ЗаполнитьКоллекциюЭлементовДереваДанныхФормы:
ДеревеЗначений и ДанныхФормыДерево имеют одинаковые имена.В большинстве случаев мы рекомендуем начинать с анализа возможности использования функции БСП ОбщегоНазначения.ЗаполнитьКоллекциюЭлементовДереваДанныхФормы, так как это наиболее простой и производительный способ. Если же ее функциональности недостаточно из-за требований к маппингу или преобразованиям, то переходите к реализации собственного рекурсивного обхода.
Вне зависимости от выбранного метода, критически важно правильно подготовить реквизит формы типа ДанныеФормыДерево. Этот реквизит должен быть добавлен в список реквизитов формы, и для него должны быть определены все необходимые колонки, которые вы ожидаете увидеть на форме. Имена этих колонок должны соответствовать тем, которые вы будете использовать в коде при заполнении (будь то автоматическое сопоставление функцией БСП или ручное).
Вы можете определить колонки для реквизита формы в конфигураторе на вкладке "Реквизиты" в свойствах реквизита, или программно при создании формы, хотя обычно предпочтительнее делать это в конфигураторе для статической структуры.
// Пример программного создания колонок для реквизита формы (обычно не рекомендуется для статических структур)
// Этот код можно добавить, например, в ПриСозданииНаСервере, если реквизит формы не имеет предопределенных колонок
// Предполагается, что ДеревоНаФорме это уже существующий реквизит формы типа ДанныеФормыДерево
Если ДеревоНаФорме.Колонки.Количество() = 0 Тогда
ДеревоНаФорме.Колонки.Добавить("Наименование", "Строка");
ДеревоНаФорме.Колонки.Добавить("Количество", "Число");
ДеревоНаФорме.Колонки.Добавить("Сумма", "Число");
ДеревоНаФорме.Колонки.Добавить("СсылкаНаОбъект", "Строка");
КонецЕсли;
Обратите внимание, что при работе с ДанныеФормыДерево, мы оперируем непосредственно с коллекциями строк: ДеревоНаФорме.Строки. Добавление новых строк всегда происходит через метод Добавить() коллекции строк. Этот подход позволяет платформе эффективно управлять отображением данных на форме.
Мы проанализировали основные подходы к переносу данных из ДереваЗначений в ДанныеФормыДерево на управляемых формах 1С. Использование функции ОбщегоНазначения.ЗаполнитьКоллекциюЭлементовДереваДанныхФормы из БСП является простым и эффективным решением для большинства стандартных сценариев, где имена колонок совпадают или частичное совпадение допустимо. Для более сложных случаев, требующих гибкого маппинга, преобразований или выборочного переноса данных, мы рекомендуем реализовать собственный рекурсивный обход. В любом случае, правильная подготовка реквизита формы с заданными колонками является фундаментальным шагом к успешному отображению древовидных данных.