При разработке в среде 1С:Предприятие 8, особенно при использовании механизмов расширений, программисты часто сталкиваются с ситуацией: значение реквизита в коде устанавливается (например, Объект.Контрагент = ВыбранноеЗначение), но связанные с ним поля (договор, цены, счета учета) не заполняются автоматически. В этой статье мы подробно разберем, почему платформа ведет себя именно так, и научимся правильно инициировать выполнение бизнес-логики.
Для начала проанализируем архитектуру управляемых форм. Важно понимать, что событие ПриИзменении — это событие элемента формы (интерфейса), а не самого реквизита объекта. Платформа генерирует это событие только в тот момент, когда пользователь интерактивно выбирает значение в поле ввода или нажимает Enter после ввода текста.
Когда мы пишем в коде Объект.Контрагент = Ссылка, мы меняем данные напрямую в структуре ДанныеФормыСтруктура. Платформа «видит» это изменение данных, но она не имитирует действия пользователя. Следовательно, цепочка событий, привязанная к визуальному контролу (элементу формы), не запускается. Рассмотрим подробнее, как выйти из этой ситуации.
Самый очевидный путь, который часто пробуют новички, — это вызвать процедуру-обработчик напрямую. Однако здесь есть свои нюансы. Если мы просто вызовем процедуру события, нам нужно передать в нее параметр Элемент, который она ожидает.
Посмотрим на пример такого вызова:
&НаКлиенте
Процедура ПЗТПсевдонимПриИзменении(Элемент)
// Программная установка контрагента
Объект.Контрагент = НайтиЮрЛицоПоУмолчанию(Объект.Псевдоним);
// Пытаемся вызвать обработчик изменения контрагента
// Передаем элемент формы, связанный с контрагентом
КонтрагентПриИзменении(Элементы.Контрагент);
КонецПроцедуры
Важный момент: если внутри КонтрагентПриИзменении используется только параметр Элемент для каких-то визуальных манипуляций, это может сработать. Но зачастую бизнес-логика в типовых конфигурациях (ERP, УТ 11, БП 3.0) запрятана глубоко в серверные вызовы, которые анализируют контекст всей формы.
Правильный подход с точки зрения чистоты кода — это отделение логики заполнения от обработчика интерфейсного события. Проанализируем типичную структуру модуля формы в качественной конфигурации. Хорошим тоном считается создание отдельной процедуры для обработки изменений.
Рассмотрим, как следует переработать код:
&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
// Вызываем общую логику заполнения
ВыполнитьЛогикуПриСменеКонтрагента();
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьЛогикуПриСменеКонтрагента()
// Здесь находится вся тяжелая логика:
// Заполнение договора по умолчанию, пересчет цен, установка валют и т.д.
// Часто здесь вызывается серверная процедура
ОбработатьКонтрагентаНаСервере();
КонецПроцедуры
Теперь, когда нам нужно программно изменить контрагента, мы просто вызываем ВыполнитьЛогикуПриСменеКонтрагента(), и нам не нужно имитировать нажатие на кнопки формы или передавать переменные элементов интерфейса.
Выясним причину одной коварной ошибки: иногда после программной установки реквизита пользователь пытается изменить его вручную на то же самое значение, но 1С блокирует вызов ПриИзменении. Это происходит потому, что значение в Объекте уже равно выбранному. Платформа сравнивает «было» и «стало», видит, что они идентичны, и не запускает событие.
Чтобы избежать проблем с рассинхронизацией состояния формы, используйте метод ОповеститьОбИзмененииРеквизита. Рассмотрим пример:
&НаКлиенте
Процедура УстановитьКонтрагентаПрограммно(НовыйКонтрагент)
Объект.Контрагент = НовыйКонтрагент;
// Сообщаем платформе, что данные реквизита изменились программно
ОповеститьОбИзмененииРеквизита("Объект.Контрагент");
// Теперь вызываем логику заполнения связанных полей
ВыполнитьЛогикуПриСменеКонтрагента();
КонецПроцедуры
Если вы пишете расширение для типовой конфигурации, выясним, как правильно «вклиниться» в существующую логику. Часто разработчики используют &Перед для подмены значения, но забывают, что основная процедура ПриИзменении может содержать проверки на заполненность или флаги изменения.
Проанализируем ситуацию, когда мы используем флаг рекурсии (как в сообщении 5 из темы форума). Это полезная практика, чтобы избежать бесконечного зацикливания, если процедуры вызывают друг друга:
&НаКлиенте
Перем г_ФлагИзменения; // Глобальная переменная модуля для предотвращения рекурсии
&НаКлиенте
Процедура Расш1_ПсевдонимПриИзмененииПосле(Элемент)
Если г_ФлагИзменения = Истина Тогда
Возврат;
КонецЕсли;
г_ФлагИзменения = Истина;
Попытка
// Программная логика
Объект.Контрагент = НайтиНужногоКонтрагента(Объект.Псевдоним);
// В типовых конфигурациях УТ/ERP/КА часто есть процедура:
// Она выполняет всю цепочку заполнения документа
ПриИзмененииКонтрагентаСервер();
Исключение
// Обработка ошибок
КонецПопытки;
г_ФлагИзменения = Ложь;
КонецПроцедуры
Если после программного вызова обработчика данные всё равно не заполняются (например, не подставляется договор), выполним следующие шаги:
ПриИзменении, которая срабатывает при ручном вводе.Элемент.ВыбранноеЗначение или специфические данные из параметров выбора. При программной установке эти свойства элемента формы могут быть пустыми.Если Объект.Контрагент = СтароеЗначение Тогда Возврат;. При программной установке «старое значение» может не определиться корректно, если вы не сохранили его заранее.КлиентСервер или ВызовСервера) получают актуальные данные из Объекта.Подведем итог: программное изменение реквизита в 1С никогда не вызывает интерфейсные события автоматически. Это осознанное поведение платформы для оптимизации производительности. Для корректной работы бизнес-логики всегда выносите алгоритмы заполнения в отдельные процедуры, которые можно вызвать как из обработчика формы, так и из любого места вашего программного кода.