При разработке управляемых форм в системе 1С:Предприятие 8.3 мы часто сталкиваемся с необходимостью задать пользователю уточняющий вопрос перед закрытием формы, например, о сохранении несохраненных данных. Эта задача становится особенно актуальной в режиме работы без модальности, характерном для веб-клиента и тонкого клиента, где использование модальных окон или прямых синхронных ожиданий недопустимо или вызывает проблемы. Мы рассмотрим, почему прямые подходы к решению этой задачи в стандартном обработчике события ПередЗакрытием оказываются неработоспособными, и какие существуют эффективные, проверенные решения.
Основная сложность возникает, когда мы пытаемся использовать асинхронные методы, такие как Ждать ВопросАсинх(), внутри обработчика события ПередЗакрытием управляемой формы, особенно когда этот обработчик помечен как Асинх. Давайте проанализируем причину такого поведения.
ПередЗакрытием: Событие ПередЗакрытием срабатывает непосредственно перед тем, как платформа начинает процесс закрытия формы. Оно предоставляет нам параметр Отказ, установка которого в Истина должна предотвратить закрытие формы.Асинх перед процедурой ПередЗакрытием, платформа начинает выполнение этой процедуры. Однако, как только встречается оператор Ждать (например, при вызове Ждать ВопросАсинх()), выполнение текущей асинхронной процедуры приостанавливается, и управление немедленно возвращается вызывающему коду платформы.ПередЗакрытием. Она продолжает процесс закрытия формы. К тому моменту, когда ВопросАсинх() завершится и вернет результат, а код в асинхронной процедуре возобновится для установки Отказ = Истина, форма уже либо будет в процессе закрытия, либо уже закроется. Параметр Отказ передается по значению, и изменение его внутри асинхронного "потока" не будет своевременно учтено основным потоком платформы, который инициировал закрытие формы. Это приводит к тому, что даже если мы установим Отказ = Истина в асинхронной процедуре, это действие будет слишком поздним или не будет иметь эффекта.Ждать без Асинх: Если мы попытаемся убрать ключевое слово Асинх из обработчика ПередЗакрытием, но при этом оставим оператор Ждать внутри, платформа выдаст ошибку, так как оператор Ждать может использоваться только в асинхронных функциях или процедурах.Обещание в платформе 8.3.18, который предназначен для работы с асинхронным кодом, и несмотря на то, что Ждать и Асинх стали более распространены, прямое применение их в ПередЗакрытием остается проблематичным из-за фундаментальной специфики обработки этого события платформой.ПередЗакрытием для отдельных форм, чтобы не нарушать общесистемные правила.Таким образом, прямой подход с использованием Асинх Процедура ПередЗакрытием и Ждать ВопросАсинх() не позволяет эффективно отменить закрытие формы, поскольку механизм передачи параметров и обработка асинхронности в этом конкретном событии платформы не соответствует нашим ожиданиям для блокировки закрытия. Рассмотрим рабочие решения.
Для корректной обработки асинхронного диалога в событии ПередЗакрытием в режиме без модальности мы применяем двухэтапный подход. Этот метод позволяет нам запросить подтверждение у пользователя, не блокируя основной поток приложения, и при этом сохранить контроль над процессом закрытия формы. Мы будем использовать механизм оповещений для асинхронной обработки ответа пользователя.
В обработчике события ПередЗакрытием мы в первую очередь безусловно устанавливаем Отказ = Истина. Это гарантирует, что форма не будет немедленно закрыта платформой. Затем мы инициируем асинхронный вопрос пользователю, используя функцию ПоказатьВопрос(), которая принимает в качестве параметра ОписаниеОповещения. Это ОписаниеОповещения содержит имя процедуры, которая будет вызвана после получения ответа от пользователя.
Для того чтобы избежать повторного вопроса при последующем программном закрытии формы (после того как пользователь уже подтвердил закрытие), мы используем клиентскую переменную-флаг, например, _ЗакрытиеПодтверждено.
// Декларация клиентской переменной-флага.
Перем _ЗакрытиеПодтверждено;
&НаКлиенте
Процедура ПередЗакрытием(Отказ, ЗавершениеРаботы, ТекстПредупреждения, СтандартнаяОбработка)
// Проверяем, если форма закрывается в рамках завершения работы всего приложения.
// В этом случае логика может быть иной или требовать безусловного закрытия.
// Для примера, мы сосредоточимся на обычном закрытии формы.
Если ЗавершениеРаботы Тогда
// Здесь можем добавить специфическую логику для завершения работы,
// или просто позволить приложению закрыться.
Возврат;
КонецЕсли;
// Если флаг _ЗакрытиеПодтверждено установлен, значит, пользователь уже ответил "Да"
// на наш вопрос, и мы хотим, чтобы форма закрылась без повторного диалога.
Если _ЗакрытиеПодтверждено Тогда
Возврат;
КонецЕсли;
// Если форма не закрывается при завершении работы и закрытие не подтверждено:
// Безусловно отменяем стандартное закрытие формы, чтобы выиграть время
// для отображения асинхронного вопроса.
Отказ = Истина;
// Создаем описание оповещения. Это объект, который указывает платформе,
// какую процедуру (и в каком контексте) вызвать после того, как пользователь
// ответит на асинхронный вопрос.
// Процедура "ОбработатьОтветНаВопросЗакрытия" будет вызвана после получения ответа.
ОписаниеОповещения = Новый ОписаниеОповещения("ОбработатьОтветНаВопросЗакрытия", ЭтотОбъект);
// Задаем асинхронный вопрос пользователю.
ПоказатьВопрос(ОписаниеОповещения, "Закрыть форму без сохранения изменений?", РежимДиалогаВопрос.ДаНет);
КонецПроцедуры
После того как пользователь ответит на асинхронный вопрос, платформа вызовет процедуру, указанную в ОписаниеОповещения (в нашем случае, ОбработатьОтветНаВопросЗакрытия). В этой процедуре мы анализируем результат ответа и принимаем решение о дальнейших действиях.
&НаКлиенте
Процедура ОбработатьОтветНаВопросЗакрытия(РезультатВопроса, ДополнительныеПараметры) Экспорт
// Анализируем результат, полученный от пользователя.
Если РезультатВопроса = КодВозвратаДиалога.Да Тогда
// Пользователь подтвердил закрытие формы без сохранения.
// Устанавливаем флаг _ЗакрытиеПодтверждено в Истина.
// Это необходимо, чтобы при следующем вызове ПередЗакрытием
// (который произойдет при Закрыть()) форма смогла успешно закрыться
// без повторного вопроса.
_ЗакрытиеПодтверждено = Истина;
// Программно вызываем метод Закрыть(), чтобы инициировать
// повторное закрытие формы, которое теперь будет успешным
// благодаря установленному флагу.
Закрыть();
КонецЕсли;
// Если пользователь отказался от закрытия (РезультатВопроса = КодВозвратаДиалога.Нет),
// никаких дополнительных действий не требуется. Форма остается открытой,
// поскольку в процедуре ПередЗакрытием параметр Отказ уже был установлен в Истина,
// предотвратив первичное закрытие.
КонецПроцедуры
Этот двухэтапный подход позволяет нам корректно управлять процессом закрытия формы, полностью соблюдая требования режима без модальности и обеспечивая интерактивность с пользователем. Мы избегаем блокировки интерфейса и обеспечиваем плавное взаимодействие.
В случаях, когда прямой перехват стандартного поведения закрытия формы через ПередЗакрытием оказывается слишком сложным или ведет к нежелательным побочным эффектам, мы можем применить обходной путь. Этот метод подразумевает блокировку стандартных способов закрытия формы и создание собственной команды на форме для управления этим процессом. Такое решение, хотя и выглядит менее "элегантным" на первый взгляд, является очень надежным и часто используется в практике, особенно когда стандартные механизмы ведут себя непредсказуемо. Если в конфигурации на базе БСП возникает проблема, когда не закрывается форма выбора дополнительной обработки, для её устранения можно использовать инструмент для исправления ошибки закрытия формы.
Мы добавляем клиентский реквизит формы (например, типа Булево) с именем ЗакрытиеФормыРазрешено и инициализируем его значением Ложь по умолчанию. В обработчике события ПередЗакрытием мы проверяем значение этого флага. Если флаг установлен в Ложь, мы устанавливаем Отказ = Истина, тем самым эффективно предотвращая любое стандартное закрытие формы.
// Создаем реквизит формы:
// Имя: ЗакрытиеФормыРазрешено
// Тип: Булево
// Использование: Клиент (или Клиент и Сервер, если требуется серверная логика)
// Значение по умолчанию: Ложь
&НаКлиенте
Процедура ПередЗакрытием(Отказ, ЗавершениеРаботы, ТекстПредупреждения, СтандартнаяОбработка)
// Если форма закрывается в рамках завершения работы всего приложения,
// возможно, мы не хотим ее блокировать.
Если ЗавершениеРаботы Тогда
Возврат;
КонецЕсли;
// Проверяем значение нашего флага.
// Если флаг ЗакрытиеФормыРазрешено установлен в Истина, значит,
// закрытие формы было инициировано нашей пользовательской командой,
// и мы позволяем ей закрыться без вопросов.
Если ЭтотОбъект.ЗакрытиеФормыРазрешено Тогда
Возврат;
КонецЕсли;
// В противном случае, когда пользователь пытается закрыть форму стандартными средствами
// (кнопка "X", Alt+F4, системное меню) и наш флаг не установлен,
// мы отменяем стандартное закрытие формы.
Отказ = Истина;
// Можно также предоставить пользователю пояснение.
ТекстПредупреждения = "На форме есть несохраненные данные. Используйте специально предназначенную кнопку 'Закрыть' для завершения работы с формой.";
КонецПроцедуры
Теперь любой стандартный способ закрытия формы (например, нажатие на кнопку "X", использование комбинации клавиш Alt+F4, или выбор команды "Закрыть" из системного меню) будет заблокирован. Пользователю будет показано предупреждение, если мы его зададим.
Мы создаем на форме новую команду, например, "Закрыть форму" или "Выйти". Эту команду мы можем разместить на панели команд формы или в другом удобном месте. В обработчике этой пользовательской команды мы реализуем всю необходимую логику: задаем асинхронный вопрос пользователю, проверяем сохраненность данных, выполняем сохранение, если необходимо, и только после этого разрешаем закрытие формы.
// Создаем команду формы:
// Имя: ЗакрытьФормуПоПользователю
// Действие: ЗакрытьФормуПоПользователю
// Размещаем на форме в удобном месте.
&НаКлиенте
Асинх Процедура ЗакрытьФормуПоПользователю(Команда)
// Предполагаем, что у формы есть свойство Модифицированность,
// которое указывает на наличие несохраненных изменений.
Если Модифицированность Тогда
// Задаем асинхронный вопрос пользователю о закрытии без сохранения.
Ответ = Ждать ВопросАсинх("На форме есть несохраненные данные. Закрыть без сохранения?", РежимДиалогаВопрос.ДаНет, , "Закрыть", "Отмена");
// Если пользователь отказался закрывать форму.
Если Ответ = КодВозвратаДиалога.Нет Тогда
Возврат; // Выходим из процедуры, отменяя закрытие.
КонецЕсли;
// Если пользователь выбрал "Да" (закрыть без сохранения),
// или если данные были сохранены ранее, или если Модифицированность = Ложь,
// продолжаем процесс закрытия.
КонецЕсли;
// Прежде чем программно закрывать форму, устанавливаем наш флаг
// ЗакрытиеФормыРазрешено в Истина. Это позволит обработчику ПередЗакрытием
// не отменять закрытие, когда оно будет вызвано методом Закрыть().
ЭтотОбъект.ЗакрытиеФормыРазрешено = Истина;
// Программно закрываем форму.
Закрыть();
КонецПроцедуры
В этом примере мы используем Ждать ВопросАсинх() внутри команды. Поскольку команда формы не является "системным" обработчиком события закрытия в том же критическом смысле, что ПередЗакрытием, асинхронные вызовы здесь работают корректно и предсказуемо. Этот подход предоставляет нам полный контроль над процессом закрытия формы, делая его управляемым и предсказуемым в режиме без модальности.
ПередЗакрытием может отличаться. Блокировка закрытия в таких сценариях может быть нежелательной. Мы рекомендуем использовать параметр ЗавершениеРаботы в процедуре ПередЗакрытием для соответствующей адаптации вашей логики, чтобы не препятствовать закрытию всего приложения.Закрыть().Использование одного из этих решений позволит нам надежно реализовать функционал подтверждения закрытия формы в управляемом режиме без модальности, обеспечивая комфортную работу пользователя и сохранность важных данных.