Как удалить несколько строк из ТаблицыЗначений в 1С?

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

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

Проблема, с которой мы сталкиваемся:

Когда мы итерируемся по коллекции (например, ТаблицеЗначений) и одновременно удаляем из нее элементы, индексы оставшихся элементов изменяются. Это означает, что если мы удалили элемент с индексом N, то элемент, который ранее был с индексом N+1, теперь занимает позицию N. При следующем шаге цикла, когда мы переходим к N+1, мы фактически пропустим этот элемент, который переместился. Именно эту проблему мы видим в исходном коде автора:


&НаКлиенте
Процедура Удалить(Команда)
    Для каждого Строка Из Реестр Цикл
        Если Строка.Выбран Тогда
            ЭтотОбъект.Реестр.Удалить(Строка);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Такой код приведёт к тому, что при удалении первой же выбранной строки, следующая строка "сдвинется", и мы можем её пропустить или получить непредсказуемое поведение. В результате, удаляются не все строки или удаление происходит по одной за несколько запусков.

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

1. Использование обратного цикла для удаления строк

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

Разберем по шагам, как это реализовать:

  1. Определяем условие, по которому строки должны быть удалены (например, поле Выбран).
  2. Используем цикл Для Индекс = Реестр.Количество - 1 По 0 С Циклом, чтобы пройти по всем строкам ТаблицыЗначений, начиная с последней и двигаясь к первой.
  3. Внутри цикла проверяем условие для каждой строки и, если оно выполняется, удаляем строку по её текущему индексу.

Посмотрим на пример кода:


&НаКлиенте
Процедура УдалитьВыбранныеСтрокиОбратнымЦиклом(Команда)
    // Предполагаем, что 'Реестр' это ТаблицаЗначений, привязанная к элементу формы,
    // и у неё есть колонка 'Выбран' типа Булево.

    Для Индекс = Реестр.Количество - 1 По 0 С Циклом
        Если Реестр[Индекс].Выбран Тогда
            Реестр.Удалить(Реестр[Индекс]);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

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

2. Формирование нового списка (ТаблицыЗначений) без удаляемых строк

Этот метод считается одним из самых безопасных, поскольку он полностью исключает модификацию коллекции во время её итерации. Вместо удаления строк из существующей ТаблицыЗначений, мы создаем новую ТаблицуЗначений и копируем в неё только те строки из исходной таблицы, которые не подлежат удалению. После этого исходная ТаблицаЗначений очищается, и в неё загружаются данные из новой, "отфильтрованной" таблицы.

Разберем этапы реализации:

  1. Создаем новую временную ТаблицуЗначений.
  2. Копируем в новую таблицу структуру колонок из исходной ТаблицыЗначений. Это можно сделать, например, перебирая колонки исходной таблицы или используя метод СкопироватьКолонки().
  3. Итерируемся по исходной ТаблицеЗначений.
  4. Для каждой строки проверяем условие на удаление. Если строка не выбрана для удаления, мы копируем её в новую ТаблицуЗначений.
  5. После завершения перебора, очищаем исходную ТаблицуЗначений методом Очистить().
  6. Загружаем данные из новой ТаблицыЗначений в исходную ТаблицуЗначений методом Загрузить().

Посмотрим на пример кода:


&НаКлиенте
Процедура УдалитьВыбранныеСтрокиЧерезНовуюТЗ(Команда)
    // Предполагаем, что 'Реестр' это ТаблицаЗначений, привязанная к элементу формы,
    // и у неё есть колонка 'Выбран' типа Булево.

    НовыйРеестр = Новый ТаблицаЗначений;

    // Копируем структуру колонок
    Для Каждого Колонка Из Реестр.Колонки Цикл
        НовыйРеестр.Колонки.Добавить(Колонка.Имя, Колонка.ТипЗначения, Колонка.Заголовок, Колонка.Ширина);
    КонецЦикла;

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

    // Очищаем исходную таблицу и загружаем в неё данные из новой
    Реестр.Очистить();
    Реестр.Загрузить(НовыйРеестр);
КонецПроцедуры

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

3. Повторный поиск и удаление первой найденной строки

Этот метод является обходным путем, который также позволяет избежать проблем с индексацией. Идея заключается в следующем: мы ищем все строки, которые нужно удалить, затем удаляем только первую из них, и после этого снова повторяем поиск. Это делается до тех пор, пока метод НайтиСтроки() не вернет пустой массив (то есть, все подлежащие удалению строки будут удалены).

Разберем шаги:

  1. Определяем условие отбора для строк, которые нужно удалить, используя структуру отбора.
  2. Вызываем метод НайтиСтроки() для исходной ТаблицыЗначений, чтобы получить массив ссылок на строки, удовлетворяющие условию.
  3. Используем цикл Пока ... Количество > 0. Внутри цикла:
    • Удаляем первую строку из полученного массива (она же является первой найденной строкой в исходной таблице).
    • Снова вызываем НайтиСтроки(), чтобы обновить массив строк для удаления, учитывая только что произошедшее удаление. Это критически важно, так как гарантирует, что поиск всегда производится по актуальному состоянию ТаблицыЗначений.

Посмотрим на пример кода, предложенный в сообщении 2:


&НаКлиенте
Процедура УдалитьВыбранныеСтрокиПовторнымПоиском(Команда)
    Отбор = Новый Структура;
    Отбор.Вставить("Выбран", Истина); // Предполагаем, что у нас есть колонка "Выбран"

    СтрокиУдалить = Реестр.НайтиСтроки(Отбор);

    Пока СтрокиУдалить.Количество > 0 Цикл
        Реестр.Удалить(СтрокиУдалить[0]); // Удаляем первую найденную строку
        СтрокиУдалить = Реестр.НайтиСтроки(Отбор); // Повторный поиск после удаления
    КонецЦикла;
КонецПроцедуры

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

4. Удаление выделенных строк в управляемой форме

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

Разберем шаги для этого случая:

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

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


&НаКлиенте
Процедура УдалитьВыделенныеСтрокиФормы(Команда)
    // Предполагаем, что 'Реестр' это имя элемента таблицы на форме,
    // а 'ЭтотОбъект.Реестр' - это сама ТаблицаЗначений, привязанная к элементу.

    // Получаем массив выделенных строк. Это массив объектов 'СтрокаТаблицыЗначений'.
    МассивВыделенныхСтрок = Элементы.Реестр.ВыделенныеСтроки;

    // Для безопасности и удобства можно пересобрать массив в обратном порядке
    // или получить индексы и удалять по ним.
    // Если МассивВыделенныхСтрок - это просто массив ссылок,
    // то удаление их по очереди из исходной ТЗ, начиная с последнего,
    // будет безопасно, так как индексы не важны, а ссылки остаются верными до удаления.

    // Создадим временный массив для удобства работы с обратным циклом,
    // если прямой проход по МассивВыделенныхСтрок с удалением не работает.
    // В данном случае, так как мы удаляем из 'ЭтотОбъект.Реестр', а не из 'МассивВыделенныхСтрок',
    // то можно итерироваться по 'МассивВыделенныхСтрок' в прямом или обратном порядке,
    // но обратный цикл всегда предпочтительнее для безопасности.

    Для Индекс = МассивВыделенныхСтрок.Количество() - 1 По 0 С Циклом
        СтрокаДляУдаления = МассивВыделенныхСтрок[Индекс];
        ЭтотОбъект.Реестр.Удалить(СтрокаДляУдаления);
    КонецЦикла;
КонецПроцедуры

Важно: свойство Элементы.Реестр.ВыделенныеСтроки возвращает массив объектов типа СтрокаТаблицыЗначений. Удаление этих объектов из самой ТаблицыЗначений (ЭтотОбъект.Реестр.Удалить(СтрокаДляУдаления)) будет корректным, если мы используем обратный цикл по массиву выделенных строк. Это позволяет избежать проблем, связанных с изменением коллекции, которую мы обходим.

Важные аспекты и рекомендации

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

← На главную