Почему после удаления строки из таблицы формы 1С выбирается неверная строка и как это исправить?

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

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

В чем заключается проблема: анализ типичной ситуации

Рассмотрим классический пример, в котором возникает ошибка. На управляемой форме расположена таблица, связанная с реквизитом типа ТаблицаЗначений. В коде формы реализована команда удаления строки и обработчик события выбора строки таблицы:


&НаКлиенте
Процедура УдалитьСтроку(Команда)
    ИндексСтроки = Элементы.СписокСловДляКонтроля.ТекущаяСтрока;
    СписокСловДляКонтроля.Удалить(ИндексСтроки);
КонецПроцедуры

&НаКлиенте
Процедура СписокСловДляКонтроляВыбор(Элемент, ВыбраннаяСтрока, Поле, СтандартнаяОбработка)
    сСписокСловДляКонтроля = СписокСловДляКонтроля[ВыбраннаяСтрока];
    текПеревод = ПолучитьПеревод(сСписокСловДляКонтроля.Слово);
    ПоказатьПредупреждение(, текПеревод);
КонецПроцедуры

Проанализируем, что происходит при выполнении этого кода:

  1. В таблице изначально присутствуют строки с индексами 0, 1, 2, 3, 4.
  2. Пользователь встает на вторую строку (индекс 1) и удаляет ее.
  3. На форме отображается обновленный список, в котором бывшая третья строка визуально становится второй.
  4. Пользователь дважды щелкает по новой второй строке (бывшей третьей), но в окне предупреждения отображаются данные четвертой строки.

Выясним причину этой ошибки: разработчик ошибочно принимает значение ТекущаяСтрока и параметр ВыбраннаяСтрока за порядковый индекс строки в таблице значений.

Разница между индексом и идентификатором строки на управляемой форме

На клиенте табличный реквизит формы представлен типом ДанныеФормыКоллекция, а строки — типом ДанныеФормыЭлементКоллекции. Принципиальные различия между индексом и идентификатором заключаются в следующем:

  1. Индекс строки — это порядковый номер элемента в коллекции, принимающий значения от 0 до Количество() - 1. При удалении строки из середины коллекции индексы всех последующих строк уменьшаются на единицу. Обращение через квадратные скобки Коллекция[Индекс] ожидает именно порядковый индекс.
  2. Идентификатор строки — это уникальный целочисленный номер (ID), который платформа автоматически присваивает каждой строке при ее добавлении в коллекцию формы. Идентификаторы строк не пересчитываются и не смещаются при удалении соседних строк.
  3. Свойства элементов формы ТекущаяСтрока, ВыделенныеСтроки, а также параметр ВыбраннаяСтрока в событиях Выбор, ОбработкаВыбора и ПриАктивизацииСтроки возвращают идентификатор строки, а не ее индекс.

В нашем примере, когда в таблице было 5 строк, им были присвоены идентификаторы 1, 2, 3, 4, 5 (или с 0 по 4 в зависимости от инициализации). После удаления строки с идентификатором 2 оставшиеся строки сохранили свои идентификаторы. При выборе третьей строки параметр ВыбраннаяСтрока вернул идентификатор 3. Обращение СписокСловДляКонтроля[3] выполнило поиск по индексу 3 (а это 4-я по порядку строка в текущей усеченной коллекции), из-за чего и возникло смещение данных.

Способ 1. Получение строки через метод НайтиПоИдентификатору

Рассмотрим базовый и наиболее надежный способ получения строки коллекции по ее идентификатору. Для этого у объекта ДанныеФормыКоллекция предназначен метод НайтиПоИдентификатору().


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

Разберем также правильное удаление строки по ее идентификатору. Метод Удалить() у коллекции данных формы умеет принимать в качестве параметра сам объект строки:


&НаКлиенте
Процедура УдалитьСтроку(Команда)
    
    ИдентификаторСтроки = Элементы.СписокСловДляКонтроля.ТекущаяСтрока;
    Если ИдентификаторСтроки <> Неопределено Тогда
        СтрокаДляУдаления = СписокСловДляКонтроля.НайтиПоИдентификатору(ИдентификаторСтроки);
        Если СтрокаДляУдаления <> Неопределено Тогда
            СписокСловДляКонтроля.Удалить(СтрокаДляУдаления);
        КонецЕсли;
    КонецЕсли;
    
КонецПроцедуры

Способ 2. Использование свойства ТекущиеДанные элемента формы

Если требуется обработать строку, на которой в данный момент установлен курсор, нет необходимости запрашивать идентификатор и затем выполнять поиск. Посмотрим на свойство ТекущиеДанные элемента таблицы формы:


&НаКлиенте
Процедура УдалитьТекущуюСтроку(Команда)
    
    ТекущаяСтрокаДанных = Элементы.СписокСловДляКонтроля.ТекущиеДанные;
    Если ТекущаяСтрокаДанных <> Неопределено Тогда
        СписокСловДляКонтроля.Удалить(ТекущаяСтрокаДанных);
    КонецЕсли;
    
КонецПроцедуры

Важно: свойство ТекущиеДанные возвращает объект строки ДанныеФормыЭлементКоллекции. Обязательно проверяйте результат на Неопределено, так как при пустой таблице или отсутствии фокуса на строке свойство не инициализировано.

Способ 3. Пакетная обработка и множественное удаление строк

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


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

Как получить порядковый индекс строки, если он действительно нужен

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


&НаКлиенте
Процедура ОпределитьИндексСтроки(Идентификатор)
    
    СтрокаДанных = СписокСловДляКонтроля.НайтиПоИдентификатору(Идентификатор);
    Если СтрокаДанных <> Неопределено Тогда
        ПорядковыйИндекс = СписокСловДляКонтроля.Индекс(СтрокаДанных);
        Сообщить("Порядковый индекс строки: " + ПорядковыйИндекс);
    КонецЕсли;
    
КонецПроцедуры

Помните, что при перезаполнении таблицы методами ЗначениеВРеквизитФормы() или Коллекция.Загрузить() платформа генерирует новые идентификаторы для всех строк, поэтому сохранять идентификаторы между серверными вызовами с полной перезагрузкой данных формы нельзя.

← На главную