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