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

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Работа с иерархическими структурами данных, такими как ДеревоЗначений, часто вызывает трудности у начинающих разработчиков в 1С. Одной из наиболее распространенных задач является копирование строк или целых веток дерева из одного объекта в другой. При этом легко совершить логические ошибки, связанные с непониманием объектной модели платформы, работы ссылочных типов данных и коллекций.

В этой статье мы подробно разберем, почему стандартные попытки «простого присваивания» строк не работают, проанализируем типичные ошибки разработчиков и выработаем элегантные, универсальные механизмы для копирования данных как для обычных объектов на сервере, так и для управляемых форм на клиенте.

Разбор типичных ошибок при работе с методом Вставить()

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


Прил5 = Контейнер.Лист02.Строки[0].Строки.Вставить(4);
Прил5 = ДеревоЗначенийЛиста2.Строки[0].Строки[4];

Выясним причину, почему этот код не выполняет поставленную задачу. Проанализируем ситуацию по шагам:

  1. На первом шаге метод Вставить(4) создает новую абсолютно пустую строку в коллекции на четвертой позиции. Метод возвращает ссылку на эту только что созданную строку, и эта ссылка записывается в переменную Прил5.
  2. На втором шаге разработчик пытается «заполнить» строку, присваивая переменной Прил5 значение другой строки из совершенно другого дерева значений (ДеревоЗначенийЛиста2).

В этот момент происходит обычное переприсваивание значения переменной. Переменная Прил5 просто начинает указывать на строку во втором дереве значений. При этом созданная на первом шаге пустая строка в первом дереве так и остается пустой и никак не изменяется, а связь с ней утеряна. Это классическая ошибка работы со ссылочными типами данных.

Еще одна частая ошибка — попытка скопировать подчиненные строки путем прямого присваивания коллекции строк:

Прил5.Строки = ДеревоЗначенийЛиста2.Строки[0].Строки[4].Строки;

Этот код вызовет ошибку выполнения. Свойство Строки у строки дерева значений имеет тип КоллекцияСтрокДереваЗначений. Данное свойство доступно только для чтения. Платформа 1С запрещает прямую замену системных коллекций. Наполнять их можно только интерактивно или программно с помощью методов Добавить() или Вставить().

Анализ неоптимального («ручного») решения

Иногда разработчики приходят к рабочему, но крайне неоптимальному решению, которое выглядит так:


Контейнер.Лист02.Строки[0].Строки[4].Строки.Очистить();
Прил5 = Контейнер.Лист02.Строки[0].Строки[4].Строки.Добавить();
Прил5.Данные = ДеревоЗначенийЛиста2.Строки[0].Строки[4].Строки[0].Данные;
Прил5.ДанныеМногострочныхЧастей = ДеревоЗначенийЛиста2.Строки[0].Строки[4].Строки[0].ДанныеМногострочныхЧастей;

Давайте разберем, почему писать такой код крайне не рекомендуется:

Правильный подход №1: Использование ЗаполнитьЗначенияСвойств()

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

Посмотрим на пример правильного копирования одиночной строки:


// Создаем новую пустую строку в нужном месте
НоваяСтрока = Контейнер.Лист02.Строки[0].Строки.Вставить(4);

// Получаем строку-источник, откуда нужно взять данные
СтрокаИсточник = ДеревоЗначенийЛиста2.Строки[0].Строки[4];

// Копируем все совпадающие колонки за один вызов
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаИсточник);

Этот подход избавляет нас от необходимости знать точную структуру колонок дерева значений. Если в дереве появятся новые колонки с одинаковыми именами, платформа перенесет их значения автоматически.

Правильный подход №2: Рекурсивное копирование всей ветки дерева

Метод ЗаполнитьЗначенияСвойств() отлично решает задачу для плоской строки, но как быть, если у копируемой строки есть подчиненные ветки (дочерние строки) любой глубины вложенности? У объекта ДеревоЗначений есть метод Скопировать(), но он копирует дерево целиком, а не отдельную его ветку.

Для решения этой задачи мы можем написать простую и эффективную рекурсивную процедуру. Рассмотрим этот алгоритм подробнее:


// Рекурсивная процедура для копирования ветки дерева значений
Процедура СкопироватьВеткуДерева(СтрокаПриемник, СтрокаИсточник) Экспорт
    
    // Копируем данные текущей строки
    ЗаполнитьЗначенияСвойств(СтрокаПриемник, СтрокаИсточник);
    
    // Обходим все подчиненные строки источника
    Для Каждого ПодчиненнаяИсточник Из СтрокаИсточник.Строки Цикл
        
        // Создаем новую подчиненную строку в приемнике
        ПодчиненнаяПриемник = СтрокаПриемник.Строки.Добавить();
        
        // Рекурсивно вызываем эту же процедуру для следующего уровня иерархии
        СкопироватьВеткуДерева(ПодчиненнаяПриемник, ПодчиненнаяИсточник);
        
    КонецЦикла;
    
КонецПроцедуры

Теперь мы можем применить эту универсальную процедуру в коде нашего примера:


НоваяСтрока = Контейнер.Лист02.Строки[0].Строки.Вставить(4);
СтрокаИсточник = ДеревоЗначенийЛиста2.Строки[0].Строки[4];

// Копируем саму строку и всю её структуру вниз по дереву
СкопироватьВеткуДерева(НоваяСтрока, СтрокаИсточник);

Такой подход гарантирует полное и точное воссоздание всей иерархической структуры данных в новом месте.

Особенности работы на управляемых формах (клиент/сервер)

При работе в управляемом интерфейсе (на управляемых формах) необходимо помнить важное архитектурное ограничение платформы 1С: объект ДеревоЗначений не может существовать на клиенте. Он живет исключительно на сервере. На клиенте данные дерева представлены специальным типом ДанныеФормыДерево, а его элементы — типом ДанныеФормыЭлементДерева.

Если вам необходимо реализовать копирование строк дерева непосредственно в кодовой бизнес-логике формы на клиенте (например, при перетаскивании строк пользователем вручную — механизм Drag-and-Drop), то напрямую обратиться к свойству .Строки не получится. Вместо него у объектов данных формы используется метод ПолучитьЭлементы().

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


&НаКлиенте
Процедура СкопироватьВеткуФормы(ЭлементПриемник, ЭлементИсточник)
    
    // Переносим данные полей текущего элемента
    ЗаполнитьЗначенияСвойств(ЭлементПриемник, ЭлементИсточник);
    
    // Получаем коллекцию подчиненных элементов источника
    ПодчиненныеИсточник = ЭлементИсточник.ПолучитьЭлементы();
    
    Для Каждого ПодчиненныйИсточник Из ПодчиненныеИсточник Цикл
        
        // Создаем элемент в коллекции приемника
        ПодчиненныйПриемник = ЭлементПриемник.ПолучитьЭлементы().Добавить();
        
        // Рекурсивно уходим на уровень ниже
        СкопироватьВеткуФормы(ПодчиненныйПриемник, ПодчиненныйИсточник);
        
    КонецЦикла;
    
КонецПроцедуры

Использование метода ПолучитьЭлементы() делает этот код полностью работоспособным в контексте тонкого или веб-клиента без лишних серверных вызовов, что значительно повышает отзывчивость интерфейса для конечного пользователя.

Важное предупреждение: защита от бесконечной рекурсии (зацикливания)

При разработке алгоритмов переноса или копирования элементов дерева значений «само в себя» (например, когда пользователь пытается скопировать или переместить родительскую строку внутрь одной из ее же дочерних строк) возникает серьезная опасность. Без предварительной проверки ваш рекурсивный алгоритм уйдет в бесконечный цикл. Это неминуемо вызовет ошибку "Переполнение стека встроенного языка" и приведет к аварийному завершению работы сеанса пользователя.

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


&НаКлиентеНаСервереБезКонтекста
Функция ПриемникЯвляетсяПотомкомИсточника(УзелПриемник, УзелИсточник)
    
    ТекущийРодитель = УзелПриемник.ПолучитьРодителя();
    
    Пока ТекущийРодитель <> Неопределено Цикл
        Если ТекущийРодитель = УзелИсточник Тогда
            Возврат Истина; // Обнаружена попытка скопировать родителя внутрь самого себя
        КонецЕсли;
        ТекущийРодитель = ТекущийРодитель.ПолучитьРодителя();
    КонецЦикла;
    
    Возврат Ложь;
    
КонецФункции

Применение такой проверки гарантирует стабильность вашей программы при выполнении любых сложных операций с иерархическими структурами в 1С.

← На главную