В практике разработки на платформе 1С:Предприятие 8.3 (Управляемые формы) часто возникает задача: выполнить тяжелую или ответственную операцию на сервере, закрыть текущую форму (например, диалоговое окно или вспомогательный помощник) и одновременно сообщить пользователю об успешном завершении операции.
Однако разработчики часто сталкиваются с неприятным эффектом: стандартный метод СообщениеПользователю или вызовы общих модулей БСП вроде ОбщегоНазначенияКлиентСервер.СообщитьПользователю() отрабатывают без ошибок, но само сообщение на экране клиента так и не появляется. Оно просто исчезает, «улетая в пустоту». В этой статье мы подробно разберем, почему так происходит, изучим внутреннюю механику платформы 1С и рассмотрим несколько надежных способов решения этой проблемы.
Для начала проанализируем, как платформа 1С:Предприятие работает с выводом сообщений в управляемом интерфейсе. Когда на сервере создается и вызывается объект СообщениеПользователю, платформа не выводит его на экран мгновенно. Вместо этого сообщения накапливаются в специальном пуле и передаются на клиентский компьютер вместе с возвратом управления (в момент завершения серверного вызова).
По умолчанию платформа привязывает накопленные сообщения к активному контексту — то есть к той форме, из которой был инициирован серверный вызов. Если в процессе обработки или сразу после возврата на клиент вызывается метод Закрыть(), текущая форма уничтожается. Вместе с контекстом закрытой формы платформа безвозвратно уничтожает и предназначенную ей панель сообщений. Именно поэтому пользователь видит лишь пустоту вместо ожидаемого уведомления.
Давайте разберем по шагам, как обойти это ограничение архитектуры и гарантировать доставку информации до пользователя.
Платформа 1С предоставляет штатный механизм для перенаправления сообщений в конкретное окно. У объекта СообщениеПользователю есть важнейшее свойство — ИдентификаторНазначения (тип УникальныйИдентификатор). Оно позволяет жестко связать вывод сообщения не с закрывающейся формой, а с любой другой формой, которая гарантированно останется открытой (например, с родительской формой списка или рабочим столом).
Рассмотрим подробнее реализацию этого механизма:
ЭтаФорма.УникальныйИдентификатор.ИдентификаторРодительскойФормы).Сообщить(), явно укажем, куда именно должно быть доставлено сообщение.Посмотрим на пример кода серверной процедуры:
&НаСервере
Процедура ВыполнитьИзменениеПараметровНаСервере()
// Выполняем полезную работу (меняем настройки, отключаем рег. задания)
ПроводитьИзменениеПараметров();
// Формируем сообщение и привязываем его к родительской форме
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Настройки базы успешно изменены на тестовые.";
Если ЗначениеЗаполнено(Объект.ИдентификаторРодительскойФормы) Тогда
Сообщение.ИдентификаторНазначения = Объект.ИдентификаторРодительскойФормы;
КонецЕсли;
Сообщение.Сообщить();
КонецПроцедуры
При таком подходе, даже если форма настроек закроется мгновенно, сообщение отобразится на панели той формы, которая инициировала весь процесс и осталась открытой на экране пользователя.
С точки зрения чистоты архитектуры управляемого приложения самым правильным решением является разделение логики: сервер должен отвечать только за проведение транзакций и обработку данных, а клиент — за взаимодействие с пользователем.
Вместо того чтобы пытаться «протолкнуть» показ интерфейсных окон непосредственно из серверного контекста, мы можем вернуть статус операции на клиент, закрыть форму и только после этого вывести красивое оповещение.
Разберем по шагам, как это реализовать:
Шаг 1. Серверная функция выполняет работу и возвращает структуру с результатом:
&НаСервере
Функция ЗаписатьНастройкиНаСервере()
Успешно = Ложь;
Попытка
// Логика отключения регламентных заданий и смены констант
ЗаписатьНовыеПараметрыРИБ();
Успешно = Истина;
Исключение
ЗаписатьВЖурналРегистрации(ОписаниеОшибки());
КонецПопытки;
Результат = Новый Структура;
Результат.Вставить("Успешно", Успешно);
Результат.Вставить("ТекстСообщения", "Параметры базы успешно адаптированы для копии.");
Возврат Результат;
КонецФункции
Шаг 2. Клиентский обработчик получает результат, корректно закрывает текущую форму и выводит всплывающее облако через метод ПоказатьОповещениеПользователя():
&НаКлиенте
Процедура ВыполнитьДействие(Команда)
РезультатОбработки = ЗаписатьНастройкиНаСервере();
Если РезультатОбработки.Успешно Тогда
// Сначала закрываем текущее диалоговое окно
ЭтаФорма.Закрыть();
// Теперь выводим всплывающее оповещение, не привязанное к форме
ПоказатьОповещениеПользователя(
"Успешно",
,
РезультатОбработки.ТекстСообщения,
БиблиотекаКартинок.Информация
);
Иначе
ПоказатьПредупреждение(, "Возникли ошибки при настройке базы.");
КонецЕсли;
КонецПроцедуры
Этот способ идеален для вывода некритичных информационных сообщений. Всплывающее окно плавно появится в правом нижнем углу экрана и никак не пострадает от того, что вызвавшая его форма уже прекратила свое существование.
Иногда серверный код является сложным, модульным или общим, из-за чего он генерирует множество мелких информационных сообщений в ходе циклов или проверок. Встраивать туда передачу уникальных идентификаторов форм неудобно.
В этом случае мы можем применить паттерн накопления сообщений. Проанализируем пример схемы:
Сообщить().Поскольку контекст старой формы уже уничтожен, платформа автоматически перенаправит все вызванные на клиенте методы Сообщить() в то окно, которое стало активным на текущий момент (например, на Начальную страницу или форму списка).
Пример реализации сбора сообщений:
&НаСервере
Функция ВыполнитьГрупповуюОбработкуНаСервере()
СписокСообщений = Новый Массив;
Для Каждого СтрокаПараметров Из Объект.ПараметрыОбмена Цикл
Попытка
ОбработатьСтрокуНастройки(СтрокаПараметров);
СписокСообщений.Добавить("Настройка " + СтрокаПараметров.Имя + " успешно изменена.");
Исключение
СписокСообщений.Добавить("Ошибка обработки: " + КраткоеПредставлениеОшибки(ОписаниеОшибки()));
КонецПопытки;
КонецЦикла;
Возврат СписокСообщений;
КонецФункции
&НаКлиенте
Процедура ВыполнитьИЗакрыть(Команда)
ЛогОпераций = ВыполнитьГрупповуюОбработкуНаСервере();
// Закрываем форму, уничтожая ее контекст
ЭтаФорма.Закрыть();
// Выводим все накопленные сообщения в активное окно
Для Каждого ТекстСообщения Из ЛогОпераций Цикл
ОбщегоНазначенияКлиент.СообщитьПользователю(ТекстСообщения);
КонецЦикла;
КонецПроцедуры
На форумах часто обсуждаются альтернативные варианты, когда у разработчика нет прямого доступа к коду родительской формы или процедуре вызова текущей формы. В таких случаях можно использовать архитектурные «обходные пути»:
Оповестить("НастройкиБазыИзменены", ПараметрыОповещения). Любая открытая на рабочем столе форма (например, «Начальная страница»), имеющая обработчик ОбработкаОповещения, перехватит это событие и выведет сообщение на свой экран.Если вы работаете в типовой конфигурации 1С (такой как 1С:ERP, 1С:Комплексная автоматизация или 1С:Управление торговлей), обязательно учитывайте стандарты разработки БСП.
Устаревший метод ОбщегоНазначенияКлиентСервер.СообщитьПользователю() во многих современных версиях библиотек разделен на контекстные вызовы:
ОбщегоНазначения.СообщитьПользователю() — предназначен строго для выполнения в серверном коде.ОбщегоНазначенияКлиент.СообщитьПользователю() — используется в клиентском контексте.Тем не менее, даже новые методы подчиняются общим правилам платформы: если вызвать серверный метод вывода сообщений без указания адресата в момент закрытия формы, они гарантированно исчезнут. Поэтому связка из передачи признака успешности выполнения на клиент и последующего вызова ПоказатьОповещениеПользователя() остается наиболее стабильным, красивым и рекомендуемым решением с точки зрения UX (пользовательского опыта).