К статье
Практический материал

Разработка и Конфигурация · 11 мин

Как в 1С скопировать дерево значений в другое дерево

Как в 1С скопировать дерево значений в другое дерево: метод Скопировать(), построчное копирование, рекурсия, работа с реквизитами формы и типичные ошибки разработчика.

Задача скопировать дерево значений в другое дерево возникает у разработчиков 1С регулярно: при дублировании данных для редактирования «копии без риска», при переносе иерархии между реквизитами формы, при подготовке данных для обмена или при фильтрации дерева без потери исходной структуры. Платформа 1С:Предприятие 8 предоставляет встроенный механизм копирования, но у него есть нюансы, которые важно понимать, чтобы не получить пустое дерево или ошибку несоответствия колонок.

Ключевой момент: дерево значений — это объект со строгой структурой колонок и иерархией строк. Простое присваивание Дерево2 = Дерево1 не создаёт копию, а лишь передаёт ссылку на тот же объект, поэтому любые изменения в «копии» затронут оригинал. Для настоящего, независимого копирования используются специальные методы, которые рассмотрены ниже.

Способ копирования выбирается в зависимости от ситуации: нужна полная копия всего дерева, копия только ветки, перенос данных между деревьями с разным составом колонок или копирование с фильтрацией строк. Для каждого сценария есть оптимальный подход.

Метод Скопировать() — полное копирование дерева

Самый простой и правильный способ — встроенный метод Скопировать() объекта ДеревоЗначений. Он создаёт полную независимую копию: структура колонок, все строки со всеми уровнями иерархии и значения ячеек переносятся в новый объект.

ДеревоИсходное = Новый ДеревоЗначений;

ДеревоИсходное.Колонки.Добавить("Номенклатура");

ДеревоИсходное.Колонки.Добавить("Количество");

// ... заполнение дерева ...

ДеревоКопия = ДеревоИсходное.Скопировать();

После выполнения ДеревоКопия — полностью самостоятельный объект. Изменение, добавление или удаление строк в нём никак не влияет на исходное дерево. Это стандартный приём, когда нужно «заморозить» состояние данных перед экспериментальной обработкой.

У метода есть необязательные параметры, позволяющие скопировать не всё дерево, а выбранные строки и колонки: Скопировать(Строки, ИменаКолонок). Например, ДеревоИсходное.Скопировать(МассивСтрок, "Номенклатура,Количество") создаст копию только из указанных строк и двух колонок. Это удобно для выборочного переноса, но требует аккуратности: строки должны принадлежать копируемому дереву.

Копирование ветки дерева: метод Скопировать() у строки

Часто нужно скопировать не всё дерево, а отдельную ветку со всеми подчинёнными строками. Для этого метод Скопировать() вызывается не у дерева, а у конкретной строки дерева (объект СтрокаДереваЗначений). Результатом будет новое дерево значений, содержащее только эту ветку.

СтрокаГруппы = ДеревоИсходное.Строки.Найти("Канцтовары", "Наименование");

Если СтрокаГруппы <> Неопределено Тогда

ДеревоВетки = СтрокаГруппы.Скопировать();

КонецЕсли;

Обратите внимание: результат — именно дерево значений, а не строка. Если нужно вставить скопированную ветку в существующее дерево как подчинённую ветку, придётся переносить её строки вручную, потому что строка одного дерева не может быть напрямую «привязана» к другому дереву.

Также у коллекции строк дерева есть метод Скопировать(), который копирует все строки верхнего уровня коллекции вместе с иерархией. Это промежуточный вариант между копированием всего дерева и одной ветки.

Рекурсивное копирование строк между деревьями с разной структурой

Когда у исходного и целевого деревьев различается состав колонок, метод Скопировать() не подходит — он переносит структуру целиком. В этом случае строки переносятся рекурсивным обходом с заполнением значений по совпадающим колонкам.

Процедура СкопироватьСтрокиДерева(СтрокиИсточник, СтрокиПриемник)

Для Каждого СтрокаИсточник Из СтрокиИсточник Цикл

НоваяСтрока = СтрокиПриемник.Добавить();

ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточник);

// рекурсивно копируем подчинённые строки

СкопироватьСтрокиДерева(СтрокаИсточник.Строки, НоваяСтрока.Строки);

КонецЦикла;

КонецПроцедуры

// вызов:

СкопироватьСтрокиДерева(ДеревоИсточник.Строки, ДеревоПриемник.Строки);

Функция ЗаполнитьЗначенияСвойств() переносит значения только по совпадающим именам колонок, поэтому лишние колонки источника игнорируются, а недостающие в приёмнике не вызывают ошибку. Если нужен точный контроль, передайте в функцию список копируемых свойств вторым параметром.

Рекурсивное копирование: порядок действий
  1. 1
    Убедитесь, что в дереве-приёмнике созданы все нужные колонки с совместимыми типами
  2. 2
    Напишите рекурсивную процедуру обхода строк источника
  3. 3
    Внутри цикла добавляйте строку в приёмник и заполняйте значения через ЗаполнитьЗначенияСвойств
  4. 4
    Вызовите процедуру рекурсивно для подчинённых строк каждой строки
  5. 5
    Проверьте результат: количество строк и значения контрольных ячеек

Альтернатива рекурсии — преобразование дерева в таблицу значений методом ДеревоЗначенийВТаблицуЗначений (в типовых конфигурациях есть аналогичные функции общих модулей), но при этом теряется иерархия, поэтому для дерева этот путь подходит редко.

Копирование дерева в реквизит формы и между формами

На управляемой форме дерево значений обычно хранится как реквизит формы типа ДеревоЗначений. Здесь действует важное ограничение: нельзя просто присвоить реквизиту новый объект, созданный в коде, — нужно использовать функцию преобразования ЗначениеВРеквизитФормы().

&НаСервере

Процедура ЗаполнитьКопиейНаСервере()

ВременноеДерево = РеквизитФормыВЗначение("ДеревоИсходное");

ДеревоКопия = ВременноеДерево.Скопировать();

ЗначениеВРеквизитФормы(ДеревоКопия, "ДеревоПриемник");

КонецПроцедуры

Обратите внимание: структуры колонок реквизитов формы ДеревоИсходное и ДеревоПриемник должны совпадать, иначе при вызове ЗначениеВРеквизитФормы возникнет ошибка или потеряются данные. Если реквизит-приёмник создаётся динамически, сначала программно добавьте колонки через ИзменитьРеквизиты().

Для передачи дерева между формами или на клиент без серверного вызова данные дерева сериализуют: преобразуют в структуру/массив структур вручную или используют ЗначениеВСтрокуВнутр() на сервере с последующим восстановлением. Прямая передача объекта ДеревоЗначений между клиентом и сервером невозможна — это частая причина ошибок у начинающих разработчиков.

Копирование с фильтрацией строк

На практике часто требуется скопировать не всё дерево, а только строки, удовлетворяющие условию, — например, только отмеченные флажками элементы вместе с их родителями. Здесь применяется рекурсивный обход с проверкой условия и сохранением пути в иерархии.

Процедура СкопироватьОтмеченные(СтрокиИсточник, СтрокиПриемник)

Для Каждого СтрокаИсточник Из СтрокиИсточник Цикл

Если СтрокаИсточник.Пометка Тогда

НоваяСтрока = СтрокиПриемник.Добавить();

ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточник);

СкопироватьОтмеченные(СтрокаИсточник.Строки, НоваяСтрока.Строки);

Иначе

// если внутри есть отмеченные — создаём родителя-«оболочку»

ВременныеСтроки = Новый Массив;

// ... логика проверки вложенных строк ...

КонецЕсли;

КонецЦикла;

КонецПроцедуры

Главная сложность фильтрованного копирования — решить заранее, что делать с родительскими строками, сами не проходящими фильтр, но содержащими отобранные подстроки. Вариантов два: копировать таких родителей как «пустые оболочки» для сохранения иерархии либо поднимать отобранные строки на верхний уровень. Выбор зависит от прикладной задачи, и его стоит зафиксировать до написания кода.

Сравнение способов копирования

Дерево.Скопировать()

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

Строка.Скопировать()

Копия одной ветки в новое дерево. Подходит для выделения поддерева; результат нельзя напрямую вставить в другое дерево

Рекурсивный обход

Полный контроль: фильтрация, разные колонки, преобразование значений. Требует написания и тестирования процедуры

ЗначениеВСтрокуВнутр / из строки

Копирование через сериализацию. Подходит для передачи между сеансами и хранения; медленнее и создаёт риски безопасности при загрузке чужих данных

Проверка результата и типичные ошибки

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

Контрольные точки

Как проверить результат копирования

Количество строк верхнего уровня
Норма

Совпадает с исходным деревом

Тревожный признак

Копия пустая или строк меньше — проверьте, что копировали .Строки, а не сам объект

Независимость объектов
Норма

Изменение значения в копии не меняет оригинал

Тревожный признак

Меняются оба дерева — выполнено присваивание вместо Скопировать()

Иерархия
Норма

Уровни вложенности и родители сохранены

Тревожный признак

Все строки на верхнем уровне — забыта рекурсия по подчинённым строкам

Значения колонок
Норма

Все нужные колонки заполнены

Тревожный признак

Часть колонок пустая — имена колонок в приёмнике не совпадают с источником

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

Типичные ошибки
01
Присваивание Дерево2 = Дерево1 вместо Скопировать() — обе переменные ссылаются на один объект, изменения затрагивают оба дерева
02
Попытка вставить строку одного дерева в другое напрямую — строка привязана к своему дереву, нужно добавлять новую строку и копировать значения
03
Копирование в реквизит формы простым присваиванием — реквизит типа ДеревоЗначений требует ЗначениеВРеквизитФормы()
04
Забыта рекурсия при ручном копировании — переносятся только строки верхнего уровня, вся вложенность теряется
05
Несовпадение имён или типов колонок при ЗаполнитьЗначенияСвойств — значения молча не переносятся, ошибки не возникает, данные теряются незаметно

Отдельно стоит упомянуть производительность: для очень больших деревьев (десятки тысяч строк) рекурсивный обход в цикле на встроенном языке работает заметно медленнее, чем штатный Скопировать(). Если структуры совпадают, всегда предпочитайте встроенный метод.

FAQ: частые вопросы о копировании деревьев значений

Можно ли скопировать дерево значений в таблицу значений?

Напрямую — нет, потому что у таблицы нет иерархии. Обычно выполняют рекурсивный обход дерева с добавлением строк в таблицу, при необходимости сохраняя уровень вложенности или ссылку на родителя в отдельных колонках. В типовых конфигурациях есть служебные функции для такого преобразования.

Почему после Скопировать() в копии пусто?

Чаще всего вызван метод у пустого дерева или у неправильной коллекции строк. Проверьте, что исходное дерево действительно заполнено на момент копирования, и что метод вызван у объекта ДеревоЗначений, а не у реквизита формы без предварительного преобразования РеквизитФормыВЗначение.

Как скопировать дерево между клиентом и сервером?

Объект ДеревоЗначений напрямую между клиентом и сервером не передаётся. На сервере дерево преобразуют в реквизит формы через ЗначениеВРеквизитФормы либо сериализуют в промежуточную структуру (массив структур) и собирают обратно на другой стороне.

Сохраняется ли сортировка при копировании?

Да, метод Скопировать() переносит строки в текущем порядке, включая результат ранее выполненной сортировки. Однако само свойство автоматического порядка строк не применяется — при последующем добавлении строк в копию порядок нужно поддерживать отдельным вызовом сортировки.

Как скопировать только часть колонок дерева?

У метода Скопировать() есть параметр с перечнем колонок: Дерево.Скопировать(, "Колонка1,Колонка2"). Либо создайте новое дерево с нужными колонками и перенесите строки рекурсивно через ЗаполнитьЗначенияСвойств с явным списком свойств.

Можно ли объединить два дерева в одно?

Да: добавьте в целевое дерево строки второго дерева рекурсивным обходом с ЗаполнитьЗначенияСвойств. Предварительно убедитесь, что состав колонок совпадает, и решите, как обрабатывать дубликаты строк верхнего уровня — объединять по ключу или добавлять как отдельные ветки.

Итог

Что важно запомнить

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