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