Почему в 1С 8.3 перестает работать копирование строк табличной части после серверного вызова?

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

При разработке сложных интерфейсов на управляемых формах 1С часто возникает задача реализации связи «Ведущая-Ведомая» (Master-Detail) между двумя табличными частями одного документа. Типичный пример: в верхней таблице мы выбираем транспортную накладную, а в нижней — видим только те товары, которые к ней относятся. Однако разработчики часто сталкиваются с неприятным «глюком»: при попытке скопировать строку в основной таблице система создает пустую строку, а стандартные обработчики событий просто перестают вызываться. Проанализируем, почему это происходит и как правильно организовать работу связанных таблиц.

Разбор исходной ситуации

Рассмотрим сценарий, с которым столкнулся автор темы. В документе есть две табличные части: ТранспортныеНакладные и ТоварныйСостав. Для их связи используется уникальный идентификатор (GUID), хранящийся в реквизите ID первой таблицы и ID_ТН во второй. Логика работы следующая:

  1. При добавлении или копировании строки в ТранспортныеНакладные генерируется новый ГУИД.
  2. При активизации строки в первой таблице срабатывает отбор во второй таблице, чтобы показать только связанные товары.
  3. При добавлении строки во вторую таблицу в нее копируется идентификатор из текущей строки первой таблицы.

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

Выясняем причину: коварство события ПриАктивизацииСтроки

Проанализируем код обработчика, который вызывал проблему. Автор использовал событие ПриАктивизацииСтроки для установки отбора во второй таблице через серверную процедуру:


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

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

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

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

Решение №1: Установка отбора на клиенте (рекомендуемое)

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

Рассмотрим, как теперь выглядит правильная процедура:


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

    // Устанавливаем отбор прямо на клиенте
    ИД_ДляОтбора = Элемент.ТекущиеДанные.ID;
    СтруктОтбора = Новый ФиксированнаяСтруктура("ID_ТН", ИД_ДляОтбора);
    
    Элементы.ТоварныйСостав.ОтборСтрок = СтруктОтбора;
    
    // Запоминаем текущий ID в переменной формы, чтобы избежать лишних срабатываний
    ТекущиеИДСтрокиТрансНакл = ИД_ДляОтбора;
    
КонецПроцедуры

Важный момент: Переменная ТекущиеИДСтрокиТрансНакл должна быть реквизитом формы (не путать с реквизитом объекта), чтобы ее значение сохранялось между вызовами клиентских процедур.

Решение №2: Использование обработчика ожидания

Если ваша логика отбора требует сложной обработки на сервере (например, нужно не просто отфильтровать строки, а дозаполнить что-то из базы данных), использовать прямой вызов в ПриАктивизацииСтроки категорически нельзя. Это не только ломает копирование, но и замедляет интерфейс (пользователь будет чувствовать «фризы» при перемещении по таблице).

Разберем, как реализовать «мягкую» активацию через ПодключитьОбработчикОжидания. Этот метод позволяет дождаться, пока пользователь прекратит быстрое перемещение по строкам, и только тогда выполнить серверное действие.

  1. В событии ПриАктивизацииСтроки мы не вызываем сервер, а только подключаем обработчик ожидания на 0.1–0.2 секунды.
  2. Если пользователь нажал «вниз» десять раз подряд, обработчик будет перезапускаться, и реальный вызов произойдет только один раз в самом конце.

&НаКлиенте
Процедура ТранспорнтыеНакладныеПриАктивизацииСтроки(Элемент)
    // Отключаем предыдущий вызов, если он еще не успел сработать
    ОтключитьОбработчикОжидания("ВыполнитьОбновлениеПодчиненнойТаблицы");
    // Подключаем новый вызов с минимальной задержкой
    ПодключитьОбработчикОжидания("ВыполнитьОбновлениеПодчиненнойТаблицы", 0.2, Истина);
КонецПроцедуры

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

Проблема пустых GUID при копировании

Разберем еще один важный нюанс. При копировании строки табличной части многие начинающие разработчики ожидают, что уникальный идентификатор скопируется автоматически. Но для связи Master-Detail нам нужен новый идентификатор в новой строке, иначе две разные накладные будут ссылаться на один и тот же состав товаров.

Посмотрим на правильную реализацию процедуры ПриНачалеРедактирования:


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

Обратите внимание: Если вы используете серверный выбор данных в этот момент, платформа может «потерять» значение Новый УникальныйИдентификатор, если оно еще не зафиксировано в данных формы. Поэтому старайтесь выполнять инициализацию простых реквизитов (таких как ГУИДы, номера, текущие даты) строго на клиенте.

Резюме и рекомендации

Проанализировав ситуацию, мы можем сформулировать золотые правила работы с табличными частями в управляемом приложении 1С:

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

← На главную