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