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

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

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

Проблема 1: Удаление в цикле обхода

Рассмотрим типичную ситуацию: разработчик пытается обойти коллекцию строк дерева через цикл Для каждого и внутри этого цикла вызывает метод Удалить(). Это приводит к тому, что часть строк «проскакивает», так как индексы элементов в коллекции смещаются сразу после удаления.

Проанализируем пример некорректного кода:


// ОШИБОЧНЫЙ ВАРИАНТ
Для каждого СтрокаДерева Из Дерево.Строки Цикл
    Если УсловиеУдаления(СтрокаДерева) Тогда
        Дерево.Строки.Удалить(СтрокаДерева); // Индексы сдвинулись, следующая строка будет пропущена!
    КонецЕсли;
КонецЦикла;

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

Решение через промежуточный массив

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

Разберем по шагам реализацию этого метода:

  1. Создадим пустой массив МассивНаУдаление.
  2. Пройдем по всем строкам текущего уровня дерева и проверим условие.
  3. Если строка подлежит удалению, добавим ее в массив.
  4. Вторым циклом пройдем по массиву и выполним удаление из коллекции.

МассивНаУдаление = Новый Массив;

// 1. Собираем строки
Для Каждого СтрокаСписка Из ТЗ.Строки Цикл
    Для каждого СтрокаНомен из СтрокаСписка.Строки Цикл
        // Проверяем условие (например, отсутствие номенклатуры в группе)
        Если НЕ СтрокаСоответствуетУсловию(СтрокаНомен) Тогда
            МассивНаУдаление.Добавить(СтрокаНомен);
        КонецЕсли;
    КонецЦикла;
КонецЦикла;

// 2. Удаляем собранные строки
Для Каждого СтрокаУдаления из МассивНаУдаление Цикл
    // Важно найти коллекцию-владельца строки
    Родитель = СтрокаУдаления.Родитель;
    Если Родитель = Неопределено Тогда
        ТЗ.Строки.Удалить(СтрокаУдаления);
    Иначе
        Родитель.Строки.Удалить(СтрокаУдаления);
    КонецЕсли;
КонецЦикла;

Проблема 2: Ошибка «Строка не принадлежит коллекции»

Выясним причину часто встречающейся ошибки: Параметр номер 1 имеет неверное значение (строка не принадлежит коллекции). В 1С метод Удалить() должен вызываться именно у той коллекции Строки, в которой непосредственно находится данный элемент. Если вы попытаетесь вызвать Дерево.Строки.Удалить(ПодчиненнаяСтрока), где подчиненная строка находится на втором или третьем уровне иерархии, система выдаст ошибку.

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

Для универсального решения задачи всегда используйте свойство Родитель объекта СтрокаДереваЗначений. Если свойство заполнено, значит, удалять нужно из коллекции Родитель.Строки. Если же Родитель равен Неопределено, значит, строка находится в корне дерева.


// Универсальная функция удаления строки из любого уровня дерева
Если СтрокаНаУдаление.Родитель <> Неопределено Тогда
    СтрокаНаУдаление.Родитель.Строки.Удалить(СтрокаНаУдаление);
Иначе
    ДеревоЗначений.Строки.Удалить(СтрокаНаУдаление);
КонецЕсли;

Решение через рекурсию для многоуровневых деревьев

Если ваше дерево имеет неопределенное количество уровней вложенности, простыми вложенными циклами не обойтись. Рассмотрим, как применить рекурсию для полной очистки дерева от ненужных данных.

Проанализируем алгоритм рекурсивного обхода:


Процедура ОчиститьДеревоРекурсивно(КоллекцияСтрок)
    
    // Используем обратный цикл, чтобы удалять на лету без массива
    Индекс = КоллекцияСтрок.Количество() - 1;
    Пока Индекс >= 0 Цикл
        ТекущаяСтрока = КоллекцияСтрок[Индекс];
        
        // Сначала идем вглубь (вниз по иерархии)
        ОчиститьДеревоРекурсивно(ТекущаяСтрока.Строки);
        
        // Теперь проверяем саму строку
        Если НужноУдалитьСтроку(ТекущаяСтрока) Тогда
            КоллекцияСтрок.Удалить(Индекс);
        КонецЕсли;
        
        Индекс = Индекс - 1;
    КонецЦикла;
    
КонецПроцедуры

Важный момент: Обратите внимание на использование цикла Пока с убывающим индексом. Это позволяет удалять элементы прямо в процессе обхода коллекции, так как удаление элемента влияет только на индексы тех объектов, которые мы уже обработали.

Альтернативные методы: НайтиСтроки

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

  1. Подготовим СтруктураОтбора.
  2. Вызовем Дерево.НайтиСтроки(СтруктураОтбора, Истина).
  3. Полученный массив строк передадим в цикл удаления, учитывая свойство Родитель.

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

Оптимизация: Запрос против Пост-обработки

Проанализируем ситуацию с точки зрения производительности. Хотя программное удаление строк из дерева — задача выполнимая, в промышленной разработке всегда лучше фильтровать данные на уровне SQL-запроса. Если ваше дерево содержит тысячи строк, обход коллекции в коде 1С может вызвать заметные задержки интерфейса.

Рекомендации по оптимизации:

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

← На главную