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