Как исправить ошибку «Недопустимое значение параметра (параметр номер '1')» при удалении строки табличной части в 1С?

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

Каждый разработчик на платформе «1С:Предприятие 8», особенно на этапе перехода от обычных форм к управляемым, сталкивается с неочевидным поведением элементов интерфейса. Одной из классических проблем является ошибка вызова метода контекста Удалить() при попытке программно удалить строку из табличной части. Симптомы этой проблемы выглядят крайне загадочно: при первом открытии формы строка удаляется без каких-либо нареканий, но стоит повторить это действие или выполнить его после добавления новых строк, как платформа выдает критическую ошибку.

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

Анализ исходной ситуации и кода

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


Совпадение = Ложь;
Для Каждого Адрес Из Объект.КонтактнаяИнформация Цикл
    Если Элементы.КонтактнаяИнформация.ТекущиеДанные.ТипАдреса = Адрес.ТипАдреса
        И НЕ Адрес.НомерСтроки = Элементы.КонтактнаяИнформация.ТекущиеДанные.НомерСтроки Тогда
        Совпадение = Истина;
    КонецЕсли;
КонецЦикла;                  
Если Совпадение = Истина Тогда
    ОчиститьСообщения();
    Сообщение = Новый СообщениеПользователю;
    Сообщение.Текст = Строка(Элементы.КонтактнаяИнформация.ТекущиеДанные.TipAdresa) + " уже заполнен. Вы можете исправить информацию...";
    Сообщение.Сообщить();
    Объект.КонтактнаяИнформация.Удалить(Элементы.КонтактнаяИнформация.ТекущаяСтрока);
КонецЕсли;

При повторном вызове этого кода на той же форме интерактивное удаление падает с системной ошибкой:

«Ошибка при вызове метода контекста (Удалить) по причине: Недопустимое значение параметра (параметр номер '1')»

Давайте выясним причину такого поведения. Для этого нам потребуется разобраться, чем отличаются понятия «индекс строки» и «идентификатор строки» в управляемом интерфейсе 1С.

Выясняем причину: Разница между индексом и идентификатором

В управляемом приложении (платформа 1С 8.2 и 8.3) таблица на форме представлена объектом типа ТаблицаФормы (в нашем случае это Элементы.КонтактнаяИнформация). Данные, которые отображаются в этой таблице, связаны с реквизитом формы, имеющим тип ДанныеФормыКоллекция (это Объект.КонтактнаяИнформация).

Метод Удалить() объекта ДанныеФормыКоллекция имеет два варианта синтаксиса:

  1. По индексу: в качестве параметра передается целое число — индекс элемента в коллекции (индексация начинается с 0). Сигнатура: Удалить(<Индекс>).
  2. По объекту строки: в качестве параметра передается непосредственно сам элемент коллекции (объект типа ДанныеФормыЭлементКоллекции). Сигнатура: Удалить(<Элемент>).

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

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

Когда вы пишите: Объект.КонтактнаяИнформация.Удалить(Элементы.КонтактнаяИнформация.ТекущаяСтрока);, вы передаете в метод Удалить() число (идентификатор). Платформа определяет тип параметра как «Число» и пытается применить первый вариант синтаксиса — удаление по индексу.

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

Если мы удаляем строку с идентификатором 1, платформа ищет строку с индексом 1, находит её и успешно удаляет. Нам кажется, что код написан правильно.

Почему происходит сбой во второй раз?
Представим, что мы удалили строку с индексом 1. В таблице осталось две строки. Теперь их индексы сместились:

Затем мы добавляем новую строку. Платформа присваивает ей следующий по порядку уникальный идентификатор (например, 3), но её физический индекс в коллекции становится равен 2:

Если теперь мы попытаемся удалить эту новую строку, свойство ТекущаяСтрока вернет число 3. Метод Удалить(3) попытается удалить элемент с индексом 3. Но в нашей коллекции всего 3 элемента (индексы 0, 1 и 2)! Элемента с индексом 3 не существует. Платформа генерирует исключение: «Недопустимое значение параметра (параметр номер '1')».

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

---

Способ №1: Удаление через свойство «ТекущиеДанные» (Рекомендуемый)

Самый простой и лаконичный способ избежать путаницы с индексами и идентификаторами — использовать свойство таблицы формы ТекущиеДанные. Это свойство сразу возвращает объект строки (тип ДанныеФормыЭлементКоллекции), который можно напрямую передать в метод Удалить().

Давайте разберем по шагам, как изменить код автора:


ТекущаяСтрокаТЧ = Элементы.КонтактнаяИнформация.ТекущиеДанные;

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

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

---

Способ №2: Использование метода «НайтиПоИдентификатору()»

Что делать, если в вашем алгоритме логика завязана именно на хранение числовых идентификаторов (например, вы передаете идентификатор строки между клиентскими и серверными процедурами)? В таком случае нам поможет метод коллекции НайтиПоИдентификатору().

Проанализируем пример реализации данного подхода:


ИдентификаторСтроки = Элементы.КонтактнаяИнформация.ТекущаяСтрока;

Если ИдентификаторСтроки <> Неопределено Тогда
    // Получаем сам объект строки по его уникальному идентификатору
    СтрокаКоллекции = Объект.КонтактнаяИнформация.НайтиПоИдентификатору(ИдентификаторСтроки);
    
    Если СтрокаКоллекции <> Неопределено Тогда
        // Теперь мы можем безопасно удалить строку, передав объект в метод Удалить
        Объект.КонтактнаяИнформация.Удалить(СтрокаКоллекции);
    КонецЕсли;
КонецЕсли;

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

---

Важное дополнение: Как правильно удалять строки в цикле

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

Рассмотрим ошибочный шаблон кода:


// ТАК ДЕЛАТЬ НЕЛЬЗЯ!
Для Каждого СтрокаАдреса Из Объект.КонтактнаяИнформация Цикл
    Если СтрокаАдреса.ТипАдреса = ПредопределенноеЗначение("Перечисление.ТипыАдресов.Неиспользуемый") Тогда
        Объект.КонтактнаяИнформация.Удалить(СтрокаАдреса);
    КонецЕсли;
КонецЦикла;

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

  1. Часть строк, идущих сразу за удаленной, будет просто пропущена (платформа их не проверит).
  2. В конце цикла может возникнуть ошибка выхода за границы коллекции.

Чтобы выполнить массовое или групповое удаление строк по условию, правильным решением будет предварительное накопление ссылок на строки в отдельный вспомогательный массив. Рассмотрим правильный алгоритм:


// 1. Создаем массив для хранения найденных строк
СтрокиДляУдаления = Новый Массив;

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

// 3. Отдельным циклом обходим массив и удаляем строки из реальной коллекции
Для Каждого СтрокаИзМассива Из СтрокиДляУдаления Цикл
    Объект.КонтактнаяИнформация.Удалить(СтрокаИзМассива);
КонецЦикла;

При использовании данного подхода смещение индексов внутри коллекции ДанныеФормыКоллекция во втором цикле уже никак не влияет на правильность удаления, поскольку мы удаляем элементы напрямую по ссылкам на объекты строк, сохраненные в массиве.

Резюме

Подводя итоги нашего разбора, сформулируем основные правила работы с удалением строк табличных частей в управляемых формах 1С:

← На главную