Как в расширении 1С добавить свой код после выполнения типового обработчика события формы?

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

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

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

Анализ проблемы: почему стандартные методы не работают так, как ожидается

Первое, что приходит на ум — использовать метод элемента формы УстановитьДействие(). Посмотрим, почему попытки его применения не приводят к желаемому результату.

Автор исходной проблемы попробовал следующий код:


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

Эта попытка завершается ошибкой "Первый параметр не верный". Давайте выясним причину. Дело в том, что ПриИзмененииПосле — это не имя события. Это специальная аннотация компилятора (&После), которая используется в коде модуля формы для указания, что процедура должна выполняться после другой процедуры. Само же событие элемента формы называется просто ПриИзменении. Метод УстановитьДействие ожидает именно имя события, а не аннотацию.

Хорошо, попробуем указать правильное имя события:

Элемент.УстановитьДействие("ПриИзменении", "МоеДействие");

Этот код выполнится без ошибок. Но результат нас не устроит: он полностью заменяет существующий типовой обработчик на наш. Стандартная логика, заложенная разработчиками конфигурации, перестанет работать. Это происходит потому, что у элемента формы на одно событие может быть назначен только один обработчик. Метод УстановитьДействие не добавляет обработчик, а перезаписывает его.

Итак, мы выяснили, что прямого программного способа "добавить" обработчик "После" не существует. Рассмотрим правильные и обходные пути решения.

Решение 1: Использование аннотации &После (рекомендуемый способ)

Наиболее корректный, надежный и поддерживаемый платформой способ расширения логики событий — это использование аннотаций в модуле формы, заимствованной в расширение. Аннотация &После была создана именно для таких задач.

Разберем по шагам, как это сделать:

  1. Заимствуйте нужную форму в свое расширение.
  2. Откройте палитру свойств элемента (например, поля "Контрагент") и посмотрите, какая процедура назначена на событие ПриИзменении. Допустим, она называется КонтрагентПриИзменении.
  3. В модуле заимствованной формы напишите свою процедуру, указав перед ней аннотацию &После с именем типового обработчика в кавычках.

Посмотрим на пример:


&После("КонтрагентПриИзменении")
&НаКлиенте
Процедура расш1_КонтрагентПриИзменении(Элемент)
    // Ваш дополнительный код, который выполнится
    // СТРОГО ПОСЛЕ типовой процедуры КонтрагентПриИзменении
    Сообщить("Сработал дополнительный обработчик для элемента: " + Элемент.Имя);
КонецПроцедуры

Плюсы этого подхода:

Минус для исходной задачи:

Как централизовать код при использовании &После

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

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

Пример реализации:

В общем модуле МойМодульКлиент:


&НаКлиенте
Процедура ЕдиныйОбработчикИзменений(Элемент, Форма) Экспорт
    // Здесь ваша единая логика для всех элементов
    Сообщить("Изменён элемент '" + Элемент.Имя + "' на форме '" + Форма.ИмяФормы + "'");
КонецПроцедуры

В модуле формы документа 1:


&После("КонтрагентПриИзменении")
&НаКлиенте
Процедура расш1_КонтрагентПриИзменении(Элемент)
    МойМодульКлиент.ЕдиныйОбработчикИзменений(Элемент, ЭтаФорма);
КонецПроцедуры

В модуле формы документа 2:


&После("ПоставщикПриИзменении")
&НаКлиенте
Процедура расш1_ПоставщикПриИзменении(Элемент)
    МойМодульКлиент.ЕдиныйОбработчикИзменений(Элемент, ЭтаФорма);
КонецПроцедуры

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

Решение 2: Программная замена с вызовом старого обработчика (теоретический способ)

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

Алгоритм мог бы выглядеть так:

  1. Получаем имя старого обработчика: ИмяСтарогоОбработчика = Элемент.ПолучитьДействие("ПриИзменении");
  2. Назначаем новый обработчик: Элемент.УстановитьДействие("ПриИзменении", "МойНовыйОбработчик");
  3. В новом обработчике вызываем старый: Внутри процедуры МойНовыйОбработчик нужно как-то исполнить код процедуры, имя которой хранится в переменной ИмяСтарогоОбработчика.

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

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

Выводы

Проанализировав возможности платформы 1С, приходим к заключению:

  1. Невозможно программно назначить обработчик, который бы выполнялся после уже существующего, не затирая его. Конструкции типа ПриИзмененииПосле не являются именами событий.
  2. Единственный правильный и поддерживаемый способ — использовать аннотацию &После("ИмяТиповогоОбработчика") в модуле формы, заимствованной в расширение.
  3. Для унификации кода при большом количестве доработок следует выносить основную логику в общий клиентский модуль, а из обработчиков &После делать только вызов этой общей процедуры.

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

← На главную