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