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