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

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

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

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

Выясняем причину: почему строка «пропадает» с формы?

Для фильтрации строк в подчиненной табличной части разработчики чаще всего используют свойство элемента формы ОтборСтрок. Данное свойство принимает объект типа ФиксированнаяСтруктура, где ключами выступают имена колонок табличной части, а значениями — условия фильтрации.

Рассмотрим стандартный жизненный цикл строки при добавлении в подчиненную табличную часть с установленным свойством ОтборСтрок:

  1. Пользователь нажимает кнопку «Добавить» в подчиненной таблице.
  2. Срабатывает событие ПриНачалеРедактирования. В этот момент разработчик программно заполняет ключевые реквизиты новой строки (например, копирует идентификатор связи или ссылку из текущей строки родительской таблицы).
  3. Пользователь заполняет остальные колонки строки на клиенте и завершает редактирование нажатием клавиши Enter.
  4. Платформа фиксирует изменения строки в данных формы на клиенте.

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

Анализ нерекомендуемого («костыльного») решения

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


&НаКлиенте
Процедура МаркировкаПриОкончанииРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования)
    Если Не ОтменаРедактирования Тогда
        // Вызов обработчика левой таблицы для принудительного перезапуска отбора
        УровниУпаковкиПриАктивизацииСтроки(Элементы.УровниУпаковки);
    КонецЕсли;
КонецПроцедуры

Проанализируем ситуацию и выясним, почему такое решение считается плохой практикой (code smell) в стандартах разработки 1С:

Решение №1: Правильный рефакторинг кода управления отборами

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

Разберем этот процесс по шагам.

Шаг 1. Создаем универсальную процедуру обновления отборов. Данная процедура будет отвечать исключительно за анализ текущего состояния родительской таблицы и установку свойства ОтборСтрок для подчиненной таблицы.


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

Шаг 2. Подключаем процедуру к событию активизации родительской таблицы. При переходе пользователя по строкам главной таблицы отбор в подчиненной будет корректно перестраиваться.


&НаКлиенте
Процедура УровниУпаковкиПриАктивизацииСтроки(Элемент)
    ОбновитьОтборМаркировки();
КонецПроцедуры

Шаг 3. Подключаем процедуру к событию окончания редактирования подчиненной таблицы. Именно этот шаг решает проблему пропадания новой строки. После нажатия Enter платформа завершит транзакцию редактирования строки, и мы принудительно переустановим отбор, заставив форму пересчитать видимость новой строки.


&НаКлиенте
Процедура МаркировкаПриОкончанииРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования)
    Если Не ОтменаРедактирования Тогда
        // Принудительно переустанавливаем отбор, чтобы платформа пересчитала видимость добавленной строки
        ОбновитьОтборМаркировки();
    КонецЕсли;
КонецПроцедуры

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

Решение №2: Использование стандартного механизма «Ключ связи» (Best Practice)

Если вы разрабатываете сложные конфигурации или хотите писать код по стандартам ведущих прикладных решений фирмы 1С (таких как ERP, Комплексная автоматизация или Управление торговлей), рекомендуется отказаться от фильтрации по бизнес-реквизитам (таким как номенклатура, характеристики или номера строк). Вместо этого используйте специальный служебный реквизит — Ключ связи.

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

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

Шаг 1. Настройка метаданных в Конфигураторе.

  1. Откройте свойства вашего документа в Конфигураторе.
  2. В табличной части УровниУпаковки (родительская) добавьте новый реквизит КлючСвязи с типом Число (длина 10, точность 0, неотрицательное).
  3. В подчиненной табличной части Маркировка добавьте аналогичный реквизит КлючСвязи с точно таким же типом данных.

Шаг 2. Программная генерация ключей связи в родительской таблице.

При добавлении строки в родительскую таблицу ей необходимо присвоить уникальный идентификатор. Для этого отлично подходит событие родительской таблицы ПередНачаломДобавления или ПриНачалеРедактирования:


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

Шаг 3. Копирование ключа связи при создании подчиненной строки.

При добавлении новой строки в подчиненную таблицу Маркировка мы автоматически копируем значение ключа из текущей выделенной строки родительской таблицы:


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

Шаг 4. Установка быстрого отбора по ключу связи.

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


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

Данную процедуру ОбновитьОтборМаркировкиПоКлючу() аналогично первому решению необходимо вызывать в обработчике ПриАктивизацииСтроки родительской таблицы и в обработчике ПриОкончанииРедактирования подчиненной таблицы.

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

← На главную