При разработке сложных интерфейсов на управляемых формах 1С часто возникает задача реализации связи «Ведущая-Ведомая» (Master-Detail) между двумя табличными частями одного документа. Типичный пример: в верхней таблице мы выбираем транспортную накладную, а в нижней — видим только те товары, которые к ней относятся. Однако разработчики часто сталкиваются с неприятным «глюком»: при попытке скопировать строку в основной таблице система создает пустую строку, а стандартные обработчики событий просто перестают вызываться. Проанализируем, почему это происходит и как правильно организовать работу связанных таблиц.
Рассмотрим сценарий, с которым столкнулся автор темы. В документе есть две табличные части: ТранспортныеНакладные и ТоварныйСостав. Для их связи используется уникальный идентификатор (GUID), хранящийся в реквизите ID первой таблицы и ID_ТН во второй. Логика работы следующая:
ТранспортныеНакладные генерируется новый ГУИД.Проблема заключается в том, что после того как пользователь добавил хотя бы одну строку во вторую таблицу, механизм копирования в первой таблице «ломается». Новая строка добавляется пустой, а процедура ПриНачалеРедактирования, где должна была произойти инициализация данных, игнорируется платформой.
Проанализируем код обработчика, который вызывал проблему. Автор использовал событие ПриАктивизацииСтроки для установки отбора во второй таблице через серверную процедуру:
&НаКлиенте
Процедура ТранспорнтыеНакладныеПриАктивизацииСтроки(Элемент)
Если Элемент.ТекущиеДанные <> Неопределено И Элемент.ТекущиеДанные.ID <> ТекущиеИДСтрокиТрансНакл Тогда
УстановитьОтборТоварногоСостава(Элемент.ТекущиеДанные.ID);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура УстановитьОтборТоварногоСостава(ИД)
СтруктОтбора = Новый ФиксированнаяСтруктура("ID_ТН",?(ИД="","заглушка",ИД));
Элементы.ТоварныйСостав.ОтборСтрок = СтруктОтбора;
ТекущиеИДСтрокиТрансНакл = ИД;
КонецПроцедуры
Основная ошибка здесь — контекстный серверный вызов внутри события активизации строки. В управляемых формах 1С 8.3 событие ПриАктивизацииСтроки срабатывает очень часто: при клике мышью, при перемещении стрелками клавиатуры и, что самое важное, в момент инициализации копирования строки.
Когда вы нажимаете кнопку «Копировать» (F9), платформа начинает процесс создания новой строки на клиенте. Если в этот микромомент срабатывает серверный вызов, происходит полная синхронизация данных между клиентом и сервером. В версиях платформы 8.3.12 и выше это часто приводит к тому, что временное состояние формы (флаг того, что сейчас идет именно копирование) сбрасывается. Платформа «забывает», что она находится в режиме интерактивного ввода, и завершает создание строки до того, как управление передается в ПриНачалеРедактирования.
Начиная с версии платформы 8.3.10, свойство ОтборСтрок доступно для изменения непосредственно на клиенте. Это позволяет избежать лишних серверных вызовов и сохранить стабильность работы интерфейса. Перепишем наш код, исключив серверную часть.
Рассмотрим, как теперь выглядит правильная процедура:
&НаКлиенте
Процедура ТранспорнтыеНакладныеПриАктивизацииСтроки(Элемент)
// Проверяем наличие данных и не изменился ли идентификатор
Если Элемент.ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
Если Элемент.ТекущиеДанные.ID = ТекущиеИДСтрокиТрансНакл Тогда
Возврат; // Отбор уже установлен для этой строки
КонецЕсли;
// Устанавливаем отбор прямо на клиенте
ИД_ДляОтбора = Элемент.ТекущиеДанные.ID;
СтруктОтбора = Новый ФиксированнаяСтруктура("ID_ТН", ИД_ДляОтбора);
Элементы.ТоварныйСостав.ОтборСтрок = СтруктОтбора;
// Запоминаем текущий ID в переменной формы, чтобы избежать лишних срабатываний
ТекущиеИДСтрокиТрансНакл = ИД_ДляОтбора;
КонецПроцедуры
Важный момент: Переменная ТекущиеИДСтрокиТрансНакл должна быть реквизитом формы (не путать с реквизитом объекта), чтобы ее значение сохранялось между вызовами клиентских процедур.
Если ваша логика отбора требует сложной обработки на сервере (например, нужно не просто отфильтровать строки, а дозаполнить что-то из базы данных), использовать прямой вызов в ПриАктивизацииСтроки категорически нельзя. Это не только ломает копирование, но и замедляет интерфейс (пользователь будет чувствовать «фризы» при перемещении по таблице).
Разберем, как реализовать «мягкую» активацию через ПодключитьОбработчикОжидания. Этот метод позволяет дождаться, пока пользователь прекратит быстрое перемещение по строкам, и только тогда выполнить серверное действие.
ПриАктивизацииСтроки мы не вызываем сервер, а только подключаем обработчик ожидания на 0.1–0.2 секунды.
&НаКлиенте
Процедура ТранспорнтыеНакладныеПриАктивизацииСтроки(Элемент)
// Отключаем предыдущий вызов, если он еще не успел сработать
ОтключитьОбработчикОжидания("ВыполнитьОбновлениеПодчиненнойТаблицы");
// Подключаем новый вызов с минимальной задержкой
ПодключитьОбработчикОжидания("ВыполнитьОбновлениеПодчиненнойТаблицы", 0.2, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьОбновлениеПодчиненнойТаблицы()
// Проверяем, есть ли текущая строка
ДанныеСтроки = Элементы.ТранспортныеНакладные.ТекущиеДанные;
Если ДанныеСтроки <> Неопределено Тогда
// Здесь можно вызвать серверный метод, если клиентского отбора недостаточно
ОбновитьДанныеНаСервере(ДанныеСтроки.ID);
КонецЕсли;
КонецПроцедуры
Разберем еще один важный нюанс. При копировании строки табличной части многие начинающие разработчики ожидают, что уникальный идентификатор скопируется автоматически. Но для связи Master-Detail нам нужен новый идентификатор в новой строке, иначе две разные накладные будут ссылаться на один и тот же состав товаров.
Посмотрим на правильную реализацию процедуры ПриНачалеРедактирования:
&НаКлиенте
Процедура ТранспортныеНакладныеПриНачалеРедактирования(Элемент, НоваяСтрока, Копирование)
// Если это новая строка или копия существующей
Если НоваяСтрока Или Копирование Тогда
// Генерируем новый уникальный идентификатор для связи
Элемент.ТекущиеДанные.ID = Новый УникальныйИдентификатор;
// Пример заполнения номера накладной на основе номера документа
Если ЗначениеЗаполнено(Объект.Номер) Тогда
Элемент.ТекущиеДанные.НомерНакладной = Объект.Номер + "/" + Формат(Элемент.ТекущиеДанные.НомерСтроки, "ЧГ=0");
КонецЕсли;
// Пересчитываем итоги, так как данные изменились
ОбновитьИтогиДокумента();
КонецЕсли;
КонецПроцедуры
Обратите внимание: Если вы используете серверный выбор данных в этот момент, платформа может «потерять» значение Новый УникальныйИдентификатор, если оно еще не зафиксировано в данных формы. Поэтому старайтесь выполнять инициализацию простых реквизитов (таких как ГУИДы, номера, текущие даты) строго на клиенте.
Проанализировав ситуацию, мы можем сформулировать золотые правила работы с табличными частями в управляемом приложении 1С:
ПриАктивизацииСтроки, ПриАктивизацииЯчейки, ПриИзменении (внутри ТЧ) — самые опасные места для серверного контекста.Элементы.Таблица.ОтборСтрок на стороне клиента для визуальной фильтрации данных.ТекущийID) в реквизитах формы, чтобы избежать лишних перерисовок интерфейса.ПриНачалеРедактирования, чтобы избежать «слипания» данных разных строк.Следуя этим правилам, вы обеспечите плавную и стабильную работу интерфейса, а механизмы платформы по копированию и добавлению строк будут работать корректно и предсказуемо.