Как работать с выделенными строками таблицы после их удаления и почему возникает ошибка "индекс за границами массива"?

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

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

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

В чем корень проблемы: Индексы vs Идентификаторы

Главная ошибка заключается в неверном представлении о том, что хранится в свойстве таблицы формы ВыделенныеСтроки. Многие начинающие разработчики полагают, что это массив с порядковыми номерами (индексами) строк. Но это не так.

Давайте четко разграничим два понятия:

  1. Индекс — это порядковый номер элемента в коллекции, который начинается с нуля. Индексы строк в данных формы (например, в Объект.Товары) — величина непостоянная. Если вы удалите первую строку, то бывшая вторая строка получит индекс 0, третья — индекс 1 и так далее. Полагаться на индексы при работе с изменяемыми данными ненадежно.
  2. Идентификатор — это уникальный числовой идентификатор, который платформа присваивает каждой строке данных формы в момент ее создания. Этот идентификатор не меняется на протяжении всего жизненного цикла строки, независимо от сортировок, добавлений или удалений других строк.

Свойство элемента формы Элементы.ИмяТаблицы.ВыделенныеСтроки содержит именно массив идентификаторов, а не индексов. Когда мы пытаемся использовать идентификатор как индекс, после удаления строк возникает конфликт: коллекция данных на объекте сократилась, ее максимальный индекс уменьшился, а мы пытаемся обратиться к ней по старому значению, которое теперь выходит за допустимые границы. Отсюда и ошибка "индекс находится за границами массива".

Коллекцию ВыделенныеСтроки не нужно "обновлять" вручную. Платформа делает это сама. Проблема не в том, что коллекция устарела, а в том, как мы используем ее содержимое.

Неправильный подход, ведущий к ошибке

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


&НаКлиенте
Процедура СформироватьОтчетОстатки(Команда)
    МассивВыделеннойНоменклатуры = Новый Массив;
    
    // ОШИБКА: "Индекс" здесь на самом деле является Идентификатором!
    Для Каждого Индекс Из Элементы.СоставЗаказа.ВыделенныеСтроки Цикл
        // Эта строка вызовет исключение после удаления строк, 
        // так как "Индекс" может оказаться больше, чем текущее количество строк.
        МассивВыделеннойНоменклатуры.Добавить(Объект.СоставЗаказа[Индекс].Номенклатура);
    КонецЦикла;
    
    // ... дальнейший код
КонецПроцедуры

Этот код будет работать корректно только до первого удаления строк. Как только пользователь удалит, например, первые 5 из 7 строк и оставит выделенными оставшиеся 2, в коллекции ВыделенныеСтроки останутся их идентификаторы (например, 5 и 6). При этом в Объект.СоставЗаказа останется всего 2 строки с индексами 0 и 1. Попытка обратиться к Объект.СоставЗаказа[5] неминуемо приведет к ошибке.

Правильное и надежное решение

Чтобы код работал стабильно, необходимо использовать идентификаторы по их прямому назначению — для поиска нужной строки в коллекции данных. Для этого у табличных частей объекта есть специальный метод НайтиПоИдентификатору().

Посмотрим на правильный алгоритм:

  1. Получаем массив идентификаторов из свойства Элементы.ИмяТаблицы.ВыделенныеСтроки.
  2. Перебираем этот массив в цикле.
  3. Внутри цикла для каждого идентификатора ищем соответствующую ему строку данных в табличной части объекта (Объект.ИмяТабличнойЧасти) с помощью метода НайтиПоИдентификатору().
  4. Если строка найдена (метод не вернул Неопределено), мы получаем прямой доступ к объекту этой строки и можем безопасно считывать ее данные.

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


&НаКлиенте
Процедура СформироватьОтчетОстатки(Команда)
    МассивВыделеннойНоменклатуры = Новый Массив;
    
    // Получаем массив идентификаторов выделенных строк
    МассивИдентификаторов = Элементы.СоставЗаказа.ВыделенныеСтроки;
    
    // Перебираем идентификаторы
    Для Каждого ИдентификаторСтроки Из МассивИдентификаторов Цикл
        // Находим строку данных по ее идентификатору. Это надежный способ.
        НайденнаяСтрока = Объект.СоставЗаказа.НайтиПоИдентификатору(ИдентификаторСтроки);
        
        // Проверяем, что строка нашлась (она могла быть удалена программно в другой процедуре)
        Если НайденнаяСтрока <> Неопределено Тогда
            // Напрямую обращаемся к данным найденной строки
            МассивВыделеннойНоменклатуры.Добавить(НайденнаяСтрока.Номенклатура);
        КонецЕсли;
    КонецЦикла;
    
    // Проверяем, что массив не пуст, прежде чем формировать отчет
    Если МассивВыделеннойНоменклатуры.Количество() > 0 Тогда
        УсловияОтбора = Новый Структура("Номенклатура", МассивВыделеннойНоменклатуры);
        ПараметрыФормы = Новый Структура("Отбор, СформироватьПриОткрытии", УсловияОтбора, Истина);
        ОткрытьФорму("Отчет.ОстаткиИДоступностьТоваров.Форма", ПараметрыФормы);
    Иначе
        // Можно выдать сообщение, что ни одной строки не выделено или не найдено
        ПоказатьПредупреждение(Неопределено, "Не выбраны строки для формирования отчета!");
    КонецЕсли;
    
КонецПроцедуры

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

Разбор промежуточных, но неоптимальных вариантов

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

Вариант 1: Поиск строки для получения ее номера


// Работает, но избыточно
Для Каждого Идентификатор Из Элементы.СоставЗаказа.ВыделенныеСтроки Цикл
    // Сначала находим строку
    НайденнаяСтрока = Объект.СоставЗаказа.НайтиПоИдентификатору(Идентификатор);
    // Затем получаем ее номер (индекс + 1)
    НомерСтрокиВТЧ = НайденнаяСтрока.НомерСтроки;  
    // А потом по индексу (номер - 1) снова обращаемся к коллекции, чтобы получить данные  
    МассивВыделеннойНоменклатуры.Добавить(Объект.СоставЗаказа[НомерСтрокиВТЧ - 1].Номенклатура);
КонецЦикла;

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

Вариант 2: Поиск строки, потом поиск индекса, потом обращение по индексу


// Тоже работает, но еще более избыточно
Для Каждого Идентификатор Из Элементы.СоставЗаказа.ВыделенныеСтроки Цикл
    СтрокаКоллекции = Объект.СоставЗаказа.НайтиПоИдентификатору(Идентификатор);
    ИндексСтрокиКоллекции = Объект.СоставЗаказа.Индекс(СтрокаКоллекции);
    МассивВыделеннойНоменклатуры.Добавить(Объект.СоставЗаказа[ИндексСтрокиКоллекции].Номенклатура);
КонецЦикла;

Здесь логика еще более усложнена. Выполняется поиск по идентификатору, затем поиск индекса найденного объекта и только потом обращение по этому индексу. Как и в прошлом примере, все это заменяется одной строкой: МассивВыделеннойНоменклатуры.Добавить(СтрокаКоллекции.Номенклатура).

Вывод

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

← На главную