При разработке на платформе 1С:Предприятие часто возникает задача выводить пользователю информационные сообщения. Однако в некоторых сценариях, например, при вводе документа на основании другого, можно столкнуться с неожиданным поведением: сообщение, которое должно появиться в новой, только что созданной форме, отображается в старой, из которой был вызван ввод на основании.
Рассмотрим типичную ситуацию: у нас есть документ, который вводится на основании другого. В обработчике события ПриСозданииНаСервере новой формы документа мы выполняем проверку и хотим сообщить пользователю некую информацию. Но вместо того, чтобы увидеть это сообщение в открывающейся форме, мы видим его в форме-источнике. Даже явное указание ИдентификаторНазначения в объекте СообщениеПользователю не решает проблему.
В этой статье мы подробно разберем, почему так происходит, и найдем надежный способ отображать сообщения именно там, где они и должны быть.
Чтобы понять корень проблемы, нужно разобраться в том, как платформа 1С обрабатывает создание форм и вывод сообщений в клиент-серверной архитектуре. Этот процесс состоит из нескольких ключевых этапов.
Когда мы инициируем ввод на основании, происходит следующее:
ОбработкаЗаполнения в модуле объекта).ПриСозданииНаСервере. На этом этапе объект формы уже существует в памяти сервера, у него даже сформирован уникальный идентификатор (УникальныйИдентификатор). Однако на стороне клиента эта форма еще не существует. Клиентское приложение еще не получило данные для её отрисовки и не создало соответствующее окно.ПриОткрытии.Теперь посмотрим, что происходит с сообщениями. В платформе 1С существует специальный буфер сообщений. Когда на сервере вызывается метод Сообщить(), сообщение не отправляется мгновенно в окно на клиенте. Оно помещается в этот буфер для последующей передачи и обработки.
Когда клиентская часть платформы обрабатывает этот буфер, она пытается найти форму-получателя. Если ей это не удается (например, потому что окно для формы с указанным идентификатором еще не создано), платформа использует запасной вариант: она выводит сообщение в текущее активное окно. В нашем сценарии с вводом на основании активным окном является как раз та форма, из которой мы начали операцию.
Таким образом, это не ошибка (баг), а особенность (фича) работы платформы. В момент выполнения ПриСозданииНаСервере для новой формы просто еще нет "места" на клиенте, куда можно было бы адресовать сообщение.
Зная причину, мы можем сформулировать надежное решение. Идея заключается в том, чтобы не выводить сообщение немедленно на сервере, а "отложить" эту операцию до того момента, когда форма будет гарантированно готова на клиенте. Идеальный момент для этого — событие ПриОткрытии.
Разберем алгоритм по шагам:
Создаем реквизит формы для хранения текста сообщения.
В дереве реквизитов формы создаваемого документа добавим новый реквизит. Назовем его, например, ТекстСообщенияПриОткрытии и зададим ему тип Строка. Важно, что это должен быть именно реквизит формы, а не реквизит объекта, чтобы его значение не сохранялось в базу данных.
На сервере сохраняем текст сообщения в этот реквизит.
В обработчике ПриСозданииНаСервере вместо вызова Сообщить() мы будем записывать текст нашего сообщения в созданный реквизит.
Посмотрим на пример:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Проверяем, что документ-основание передан
Если ЗначениеЗаполнено(Параметры.Основание) Тогда
// Выполняем проверку. Например, хотим предупредить, если основание уже проведено
Если Параметры.Основание.Проведен Тогда
// Вместо прямого вывода сообщения, сохраняем его текст в реквизит формы
ТекстСообщенияПриОткрытии = "Внимание! Документ-основание уже проведен. Проверьте корректность данных.";
КонецЕсли;
КонецЕсли;
КонецПроцедуры
На клиенте выводим сообщение из реквизита.
Теперь в обработчике события ПриОткрытии, которое выполняется на клиенте, мы проверим, заполнено ли наше "хранилище". Если да — выведем сообщение стандартным способом. На этом этапе форма уже является активной, и сообщение отобразится именно в ней.
Пример кода:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
// Проверяем, есть ли текст для вывода
Если Не ПустаяСтрока(ТекстСообщенияПриОткрытии) Тогда
// Создаем и настраиваем объект сообщения
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = ТекстСообщенияПриОткрытии;
// При необходимости можно привязать сообщение к конкретному полю
// Сообщение.Поле = "Объект.Контрагент";
// Выводим сообщение
Сообщение.Сообщить();
КонецЕсли;
КонецПроцедуры
Этот подход является надежным и рекомендованным, так как он четко разделяет логику подготовки данных на сервере и логику их отображения на клиенте, учитывая особенности жизненного цикла управляемой формы.
Описанный выше метод идеально подходит для информационных сообщений и предупреждений, которые не должны прерывать работу пользователя. Но что если проверка в ПриСозданииНаСервере выявляет критическую ошибку, из-за которой форма вообще не должна открываться?
В таком случае использовать СообщениеПользователю нецелесообразно. Стандартным и правильным механизмом для таких ситуаций является генерация исключения.
На сервере следует использовать конструкцию ВызватьИсключение:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Если Не ПроверкаПравДоступа() Тогда
// Прерываем создание формы и выводим пользователю текст ошибки
ВызватьИсключение "Недостаточно прав для создания документа!";
КонецЕсли;
КонецПроцедуры
При вызове исключения выполнение кода на сервере прерывается, форма на клиент не передается и, соответственно, не открывается. Платформа сама позаботится о том, чтобы корректно отобразить текст исключения пользователю.
В итоге, понимание клиент-серверного взаимодействия и жизненного цикла формы позволяет выбрать правильный инструмент для каждой задачи: отложенный вывод для информационных сообщений и механизм исключений для критических ошибок.