Как устроены события платформы 1С:Предприятие 8.3 и где писать их обработчики
Содержание
Событие в 1С — это момент, в который платформа сама вызывает процедуру с заранее известным именем: перед записью объекта, при открытии формы, при изменении поля, при старте сеанса. Разработчик не вызывает такую процедуру из кода. Он пишет обработчик в нужном модуле, а платформа запускает его в нужный момент и передаёт параметры, например Отказ.
Связаны события с тремя вещами: с объектом конфигурации (справочник, документ, регистр), с формой и её элементами и с приложением в целом. Отдельный механизм — подписки на события: они позволяют повесить один обработчик на десятки объектов, не трогая их модули. Всё ниже относится к платформе 8.3 и управляемым формам, от конкретной конфигурации не зависит.
Суть механизма: кто вызывает обработчик и что он может изменить
Обработчик события — обычная процедура со строго заданным именем и набором параметров. Имя и параметры определяет платформа, а не программист. Если ошибиться в имени (ПередЗапись вместо ПередЗаписью), процедура просто не вызовется, и сообщения об ошибке не будет.
Что даёт обработчик:
- 🛑 Отменить действие. Почти у всех событий с приставкой «Перед…» есть параметр
Отказ. УстановилиОтказ = Истина— запись, удаление или закрытие формы не произойдёт. - ✏️ Изменить данные. В
ПередЗаписьюможно дозаполнить реквизиты, и они запишутся вместе с объектом. - 🔁 Заменить стандартное поведение. Параметр
СтандартнаяОбработка = Ложьотключает то, что платформа сделала бы сама, например стандартный выбор из списка. - 📣 Отреагировать постфактум. События без «Перед…» (
ПриЗаписи,ПослеЗаписи,ПриЗакрытии) сообщают, что действие уже выполнено.
Обработчики объекта выполняются на сервере, обработчики формы — на клиенте или на сервере, в зависимости от события. Это видно по суффиксу: ПриСозданииНаСервере идёт на сервере, ПриОткрытии — на клиенте.
Виды событий в 1С и модули, где они живут
Удобнее всего классифицировать события по месту, где пишется обработчик. Каждому модулю соответствует свой набор событий.
| Модуль | Примеры событий | Где выполняется |
|---|---|---|
| Модуль объекта (справочник, документ) | ПередЗаписью, ПриЗаписи, ОбработкаЗаполнения, ОбработкаПроверкиЗаполнения, ПередУдалением, ПриКопировании, ОбработкаПроведения | Сервер |
| Модуль набора записей регистра | ПередЗаписью, ПриЗаписи | Сервер |
| Модуль менеджера | ОбработкаПолученияДанныхВыбора, ОбработкаПолученияФормы, ОбработкаПолученияПредставления | Сервер |
| Модуль формы | ПриСозданииНаСервере, ПриОткрытии, ПередЗакрытием, ОбработкаОповещения, ПередЗаписью, ПослеЗаписи | Клиент и сервер |
| Элемент формы | ПриИзменении, НачалоВыбора, ОбработкаВыбора, Очистка | Клиент |
| Модуль сеанса | УстановкаПараметровСеанса | Сервер |
| Модуль управляемого приложения | ПередНачаломРаботыСистемы, ПриНачалеРаботыСистемы, ПередЗавершениемРаботыСистемы, ПриЗавершенииРаботыСистемы | Клиент |
Самый быстрый способ создать обработчик без опечаток — не писать его руками. В конфигураторе откройте модуль объекта и выберите событие в списке процедур над текстом модуля, либо в форме откройте свойства элемента и нажмите кнопку с лупой у нужного события на закладке событий. Конфигуратор сам создаст процедуру с правильным именем и параметрами.
Пример: запрет записи без контрагента
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Если Не ЗначениеЗаполнено(Контрагент) Тогда
ОбщегоНазначения.СообщитьПользователю("Не указан контрагент", ЭтотОбъект, "Контрагент");
Отказ = Истина;
КонецЕсли;
КонецПроцедуры
Модуль ОбщегоНазначения есть в типовых конфигурациях на БСП. В своей конфигурации без БСП используйте объект СообщениеПользователю.
Проверку заполненности обязательных реквизитов лучше делать не в ПередЗаписью, а в ОбработкаПроверкиЗаполнения или через свойство реквизита «Проверка заполнения». Тогда платформа сама подсветит поле в форме.
Порядок событий при записи и проведении документа
Чаще всего путаница возникает с вопросом «какой обработчик сработает раньше». Для документа, который пользователь записывает и проводит из формы, порядок такой:
ПередЗаписью— модуль формы, клиент. Можно отменить запись ещё до обращения к серверу.- Проверка заполнения:
ОбработкаПроверкиЗаполненияв модуле объекта иОбработкаПроверкиЗаполненияНаСерверев форме. ПередЗаписьюНаСервере— модуль формы. Есть параметрТекущийОбъект, это уже объект, а не данные формы.ПередЗаписью— модуль объекта. Отсюда начинается транзакция.ПриЗаписи— модуль объекта. Данные документа уже записаны в базу внутри транзакции.ОбработкаПроведения— модуль объекта, формирование движений по регистрам.ПриЗаписиНаСервереиПослеЗаписиНаСервере— модуль формы.ПослеЗаписи— модуль формы, клиент.
Если на любом шаге с 4-го по 6-й выставить Отказ = Истина, транзакция откатится целиком: ни документ, ни движения не запишутся.
При записи из кода (ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение)) форменных событий нет: выполняются только шаги 4–6 и подписки.
Открытие формы существующего объекта идёт так: ПриЧтенииНаСервере → ПриСозданииНаСервере → ПриОткрытии. Закрытие: ПередЗакрытием → ПриЗакрытии.
Подписки на события: один обработчик для многих объектов
Подписка на событие — объект конфигурации в ветке Общие → Подписки на события. У неё три главных свойства:
- Источник — список типов объектов, на которые она реагирует, например
ДокументОбъект.РеализацияТоваровУслугиДокументОбъект.ПоступлениеТоваровУслуг. - Событие — какое событие перехватывать:
ПередЗаписью,ПриЗаписи,ОбработкаПроведения,ПередУдалениеми другие. - Обработчик — экспортная процедура общего модуля.
Как создать подписку
- Создайте общий модуль, например
ПодпискиКонтроль. Отметьте флажок «Сервер», флажки «Клиент» и «Вызов сервера» не нужны. - В ветке
Общие → Подписки на событиядобавьте новый объект. - В свойстве «Источник» отметьте нужные типы объектов.
- В свойстве «Событие» выберите событие.
- В свойстве «Обработчик» укажите процедуру. Если её ещё нет, конфигуратор предложит создать её в выбранном модуле с правильными параметрами.
Пример обработчика подписки
Процедура КонтрольДатыПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Если Источник.Дата > КонецДня(ТекущаяДатаСеанса()) Тогда
Отказ = Истина;
ВызватьИсключение "Нельзя записать документ будущей датой";
КонецЕсли;
КонецПроцедуры
Первый параметр Источник — сам объект, который записывается. Остальные параметры повторяют параметры события в модуле объекта.
Ключевые правила подписок:
- ⏱ Обработчик подписки вызывается после обработчика с тем же именем в модуле объекта.
- 🔀 Порядок вызова нескольких подписок на одно событие платформа не гарантирует. Не стройте логику, где одна подписка зависит от результата другой.
- 📦 Типовые конфигурации активно используют подписки: регистрация изменений для обменов, версионирование объектов, контроль дат запрета изменения. Свою подписку проще добавить через расширение, чем менять типовую конфигурацию .
События формы: от создания до изменения поля
События формы делятся на два уровня: события самой формы и события её элементов.
События формы
- 🟢
ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)— первое событие. Здесь заполняют реквизиты формы, настраивают видимость элементов, читают параметры открытия черезПараметры. - 🟢
ПриОткрытии(Отказ)— клиент, форма вот-вот появится на экране. Подходит для подключения обработчиков ожидания. - 🟡
ПередЗакрытием(Отказ, ЗавершениеРаботы, ТекстПредупреждения, СтандартнаяОбработка)— можно спросить «Сохранить изменения?» или запретить закрытие. - 🔴
ПриЗакрытии(ЗавершениеРаботы)— форма уже закрывается, отменить нельзя. - 📣
ОбработкаОповещения(ИмяСобытия, Параметр, Источник)— приём сообщений от других форм. - ↩️
ОбработкаВыбора(ВыбранноеЗначение, ИсточникВыбора)— сюда возвращается значение из формы, открытой в режиме выбора.
События элементов
ПриИзменении(Элемент)— пользователь изменил значение поля и ушёл из него. Типичная задача: при смене контрагента подставить договор.НачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)— нажата кнопка выбора. Здесь можно открыть свою форму вместо стандартной.Очистка(Элемент, СтандартнаяОбработка)— пользователь очистил поле.
&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
КонтрагентПриИзмененииНаСервере();
КонецПроцедуры
&НаСервере
Процедура КонтрагентПриИзмененииНаСервере()
Объект.Договор = Справочники.ДоговорыКонтрагентов.ПустаяСсылка();
КонецПроцедуры
ПриИзменении срабатывает только при интерактивном изменении. Если значение поля поменял код (Объект.Контрагент = ...), обработчик не вызовется — его нужно вызвать вручную.
Обработчик элементу можно назначить и программно, например у поля, созданного в ПриСозданииНаСервере:
НовоеПоле.УстановитьДействие("ПриИзменении", "ДинамическоеПолеПриИзменении");
Процедура с таким именем должна существовать в модуле формы с директивой &НаКлиенте.
Свои события: Оповестить, ОбработкаОповещения и расширения
Объявить «новое событие» в конфигураторе, как в объектных языках, нельзя. Но есть три штатных способа построить собственный событийный обмен.
1. Оповещение между формами
Форма-отправитель вызывает глобальный метод на клиенте:
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
Оповестить("ЗаписанЗаказ", Объект.Ссылка, ЭтаФорма);
КонецПроцедуры
Все открытые формы получают вызов ОбработкаОповещения. Каждая сама решает, реагировать ли:
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
Если ИмяСобытия = "ЗаписанЗаказ" Тогда
Элементы.Список.Обновить();
КонецЕсли;
КонецПроцедуры
Имя события — произвольная строка. Договоритесь о понятных именах и храните их в одном месте, иначе через год никто не найдёт, кто шлёт «Обновить1».
Оповещение работает только в пределах клиентского приложения одного пользователя. Форма другого пользователя его не получит.
2. Описание оповещения для асинхронных вызовов
ОписаниеОповещения — это «обработчик, который вызовется потом»: после ответа на вопрос, закрытия формы, выбора файла.
Оповещение = Новый ОписаниеОповещения("ПослеОтветаНаВопрос", ЭтотОбъект);
ПоказатьВопрос(Оповещение, "Перезаполнить табличную часть?", РежимДиалогаВопрос.ДаНет);
Процедура ПослеОтветаНаВопрос(Результат, ДополнительныеПараметры) получит выбор пользователя.
3. Перехват событий в расширении
Расширение конфигурации (платформа 8.3.6 и выше) умеет добавить свою логику к событию типового объекта, не снимая конфигурацию с поддержки. Для этого используют аннотации:
&После("ПередЗаписью")
Процедура Расш1_ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// своя проверка после типового кода
КонецПроцедуры
&Перед— ваш код выполнится до типового обработчика.&После— после типового.&Вместо— вместо типового; вызвать исходный код можно черезПродолжитьВызов(...).
Это главный приём практика: доработка обработчика через расширение переживает обновление типовой конфигурации, правка модуля объекта — нет.
Ошибки и подводные камни при работе с событиями
Ещё несколько нюансов, которые не видны сразу:
- 🔄 Рекурсия. Вызов
Записать()того же объекта внутри егоПриЗаписизапускает событие снова. Нужно менять данные — делайте это вПередЗаписью, там запись ещё не произошла. - 🧱 Транзакция. Всё, что выполняется в
ПередЗаписью,ПриЗаписииОбработкаПроведения, — внутри одной транзакции. Долгий HTTP-запрос к внешнему сервису в этих обработчиках держит блокировки и тормозит других пользователей. - 🧭 Контекст. В модуле объекта нет доступа к форме, а в клиентском обработчике формы нет доступа к базе. Данные с сервера на клиент передавайте через серверную процедуру формы.
- 👥 Права. Обработчик выполняется с правами текущего пользователя. Если нужно прочитать данные, к которым у него нет доступа, используйте привилегированный режим или привилегированный общий модуль.
Практические сценарии: какое событие выбрать под задачу
| Задача | Событие | Модуль |
|---|---|---|
| Заполнить новый документ значениями по умолчанию | ОбработкаЗаполнения | Модуль объекта |
| Заполнить документ на основании другого | ОбработкаЗаполнения, параметр ДанныеЗаполнения | Модуль объекта |
| Запретить запись с пустым или неверным реквизитом | ОбработкаПроверкиЗаполнения | Модуль объекта |
| Очистить служебные поля при копировании | ПриКопировании | Модуль объекта |
| Сформировать движения по регистрам | ОбработкаПроведения | Модуль объекта |
| Запретить удаление элемента, который где-то используется | ПередУдалением | Модуль объекта |
| Одна проверка для 20 видов документов | Подписка на ПередЗаписью | Общий модуль |
| Подставить договор при смене контрагента | ПриИзменении поля | Модуль формы |
| Обновить список после записи документа в другой форме | Оповестить + ОбработкаОповещения | Модули форм |
| Отфильтровать выпадающий список при вводе по строке | ОбработкаПолученияДанныхВыбора | Модуль менеджера |
| Заполнить параметры сеанса при входе | УстановкаПараметровСеанса | Модуль сеанса |
| Показать предупреждение при запуске программы | ПриНачалеРаботыСистемы | Модуль управляемого приложения |
Мини-кейс: автозаполнение ответственного
Задача: в каждом новом документе поле «Ответственный» должно заполняться текущим пользователем. Документов 15.
Плохое решение — 15 одинаковых обработчиков ОбработкаЗаполнения. Хорошее — одна подписка на ОбработкаЗаполнения с этими 15 документами в «Источнике» и обработчик в общем модуле:
Процедура ЗаполнитьОтветственного(Источник, ДанныеЗаполнения, ТекстЗаполнения, СтандартнаяОбработка) Экспорт
Если Не ЗначениеЗаполнено(Источник.Ответственный) Тогда
Источник.Ответственный = Пользователи.ТекущийПользователь();
КонецЕсли;
КонецПроцедуры
Пользователи.ТекущийПользователь() — функция БСП. Добавился 16-й документ — отметили его в подписке, код не трогаете.