Как правильно задать вопрос пользователю перед закрытием формы и не заблокировать выход из 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При проектировании интерфейсов в «1С:Предприятии 8.3» разработчики часто сталкиваются с задачей интерактивного взаимодействия с пользователем при закрытии окон. Типичный пример — необходимость подтверждения выхода из рабочего места, закрытия сложной обработки или предупреждения о потере несохраненных данных. Однако простая и очевидная на первый взгляд реализация через немодальные диалоги способна полностью нарушить штатный процесс закрытия всего приложения.

В рамках этой статьи мы подробно разберем, почему старые методы опроса приводят к «зависанию» программы при выходе, проанализируем внутренние механизмы платформы и рассмотрим современные правильные шаблоны написания кода — как чисто средствами платформы, так и с использованием механизмов Библиотеки Стандартных Подсистем (БСП).

Почему классический подход ломает закрытие приложения?

Рассмотрим типичную ошибку проектирования. Разработчик пытается реализовать вопрос перед закрытием формы, используя флаг подтверждения (переменную на клиенте) и стандартный метод ПоказатьВопрос. Логика выглядит следующим образом:

  1. Пользователь пытается закрыть форму.
  2. Срабатывает событие ПередЗакрытием.
  3. Программа видит, что флаг ПодтвержденоЗакрытиеФормы равен Ложь, устанавливает Отказ = Истина (прерывая закрытие) и асинхронно выводит диалоговое окно с вопросом.
  4. Если пользователь нажимает «Да», программа устанавливает флаг ПодтвержденоЗакрытиеФормы = Истина и принудительно вызывает метод Закрыть().
  5. При повторном вызове события ПередЗакрытием обработчик видит установленный флаг и безболезненно пропускает закрытие.

Проблема этой схемы проявляется тогда, когда пользователь закрывает не отдельную форму, а все приложение целиком (например, нажимая на крестик главного окна программы или используя системное меню). В этот момент платформа инициирует закрытие всех открытых окон. Для каждого из них вызывается событие ПередЗакрытием. Как только наша форма обрабатывает это событие, она при первом проходе принудительно устанавливает параметр Отказ = Истина, чтобы дождаться ответа пользователя в немодальном окне.

Для платформы установка Отказ = Истина в любой из открытых форм является жестким сигналом: процесс закрытия всего приложения должен быть немедленно остановлен. Даже если пользователь через секунду нажмет кнопку «Да» в диалоговом окне, и форма закроется, само приложение уже не закроется, так как глобальная транзакция завершения работы системы была прервана.

Разбор штатных параметров события ПередЗакрытием

Чтобы решить эту архитектурную проблему, начиная с версии платформы 8.3.8, разработчики «1С» расширили состав параметров события ПередЗакрытием. Рассмотрим полную сигнатуру этого обработчика:


&НаКлиенте
Процедура ПередЗакрытием(Отказ, ЗавершениеРаботы, ТекстПредупреждения, СтандартнаяОбработка)
    // Код обработчика
КонецПроцедуры

Выясним назначение ключевых параметров, которые помогут нам обойти проблему блокировки:

Решение №1: Прямая обработка параметров на чистой платформе

Проанализируем правильный алгоритм работы с использованием штатных параметров события. Нам нужно разделить логику на два сценария: закрывается только одна форма или закрывается все приложение.

Если закрывается только форма (ЗавершениеРаботы = Ложь), мы можем использовать стандартную схему с немодальным вопросом и флагом-реквизитом формы ПодтвержденоЗакрытиеФормы.

Если же закрывается все приложение (ЗавершениеРаботы = Истина), нам категорически запрещено открывать немодальные окна через ПоказатьВопрос. Вместо этого мы должны передать наш вопрос платформе через параметр ТекстПредупреждения и установить Отказ = Истина.

Рассмотрим пример реализации этого алгоритма в коде управляемой формы:


&НаКлиенте
Перем ПодтвержденоЗакрытиеФормы; // Локальная переменная модуля для контроля повторного входа

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

&НаКлиенте
Процедура ПослеВопросаОЗакрытии(Результат, ДополнительныеПараметры) Экспорт
    
    Если Результат = КодВозвратаДиалога.Да Тогда
        // Устанавливаем флаг, чтобы при повторном вызове не уйти в бесконечный цикл
        ПодтвержденоЗакрытиеФормы = Истина;
        Закрыть();
    КонецЕсли;
    
КонецПроцедуры

// Инициализируем флаг при открытии формы или в теле модуля клиента
ПодтвержденоЗакрытиеФормы = Ложь;

Как работает этот код? При попытке закрыть программу 1С видит, что параметр ЗавершениеРаботы равен Истина. Код записывает текст в ТекстПредупреждения и взводит флаг Отказ. Платформа собирает такие предупреждения со всех открытых форм и выводит пользователю одно общее системное окно со списком всех предупреждений. Если пользователь нажимает «Продолжить работу», процесс завершения прерывается. Если нажимает «Завершить работу» — приложение закрывается принудительно, игнорируя дальнейшие проверки.

Решение №2: Использование стандартных механизмов БСП

Если вы работаете в конфигурации, созданной на базе Библиотеки Стандартных Подсистем (например, «1С:ERP», «1С:Комплексная автоматизация», «1С:Управление торговлей» или «1С:Бухгалтерия»), вам не нужно вручную прописывать логику с переменными-флагами и оповещениями. В БСП для этого существуют готовые высокоуровневые методы.

Для произвольных форм (обработок, отчетов, рабочих столов) в БСП предусмотрена процедура ОбщегоНазначенияКлиент.ПоказатьПодтверждениеЗакрытияПроизвольнойФормы(). Чтобы интегрировать её, выполним несколько простых шагов:

  1. Добавим в реквизиты нашей формы новый реквизит с именем ЗакрытьФормуБезПодтверждения (тип Булево).
  2. В обработчике события ПередЗакрытием вызовем процедуру БСП, передав в нее параметры.

Посмотрим на пример реализации:


&НаКлиенте
Процедура ПередЗакрытием(Отказ, ЗавершениеРаботы, ТекстПредупреждения, СтандартнаяОбработка)
    
    ТекстВопроса = НСтр("ru = 'Вы действительно хотите закрыть рабочее место?'");
    
    // Вызываем стандартный метод БСП, который берет на себя всю рутину
    ОбщегоНазначенияКлиент.ПоказатьПодтверждениеЗакрытияПроизвольнойФормы(
        ЭтотОбъект, 
        Отказ, 
        ЗавершениеРаботы, 
        ТекстВопроса, 
        "ЗакрытьФормуБезПодтверждения"
    );
    
КонецПроцедуры

Этот метод БСП под капотом выполняет все необходимые действия: корректно анализирует контекст закрытия (включая работу в веб-клиенте, где действуют жесткие ограничения браузеров на показ диалогов), правильно заполняет параметры отказа и предупреждения системы, а также использует добавленный нами реквизит формы в качестве флага обхода при повторном вызове закрытия.

Асинхронные вызовы и сохранение данных при закрытии

Иногда задача усложняется: при закрытии приложения требуется не просто спросить пользователя, но и выполнить какое-то асинхронное действие (например, записать настройки на сервере или закрыть кассовую смену). Напрямую вызывать серверные методы или открывать окна в момент завершения работы системы в современных версиях платформы запрещено.

Для решения подобных задач применяется паттерн с подключением обработчика ожидания. Рассмотрим логику его работы:

  1. При закрытии в событии ПередЗакрытием мы определяем, что требуется выполнить запись данных.
  2. Мы устанавливаем Отказ = Истина, отменяя закрытие, заполняем ТекстПредупреждения, чтобы показать его пользователю, и одновременно запускаем однократный обработчик ожидания с минимальным интервалом (например, 0.1 секунды).
  3. Если пользователь подтвердил закрытие всей программы, приложение просто завершает работу.
  4. Если пользователь передумал и нажал кнопку «Продолжить работу», управление возвращается в интерфейс. Через доли секунды срабатывает наш обработчик ожидания, который в фоновом режиме, безопасно для системы, выполняет серверный вызов записи данных или запуск необходимых процедур.

Применим этот паттерн на практике:


&НаКлиенте
Процедура ПередЗакрытием(Отказ, ЗавершениеРаботы, ТекстПредупреждения, СтандартнаяОбработка)
    
    Если Не ДанныеСохранены Тогда
        Отказ = Истина;
        ТекстПредупреждения = НСтр("ru = 'Внимание! Данные сеанса не сохранены. Завершить работу?'");
        
        // Подключаем обработчик ожидания для фонового сохранения
        // на случай, если пользователь решит остаться в программе
        ПодключитьОбработчикОжидания("ВыполнитьФоновоеСохранениеДанных", 0.1, Истина);
    КонецЕсли;
    
КонецПроцедуры

&НаКлиенте
Процедура ВыполнитьФоновоеСохранениеДанных()
    
    // Здесь безопасно вызываем серверные процедуры для записи состояния
    СохранитьСостояниеНаСервере();
    ДанныеСохранены = Истина;
    
    ПоказатьОповещениеПользователя(НСтр("ru = 'Сохранение завершено успешно'"));
    
КонецПроцедуры

Подведем итоги

Правильное проектирование интерфейса закрытия окон в «1С:Предприятии» требует понимания асинхронной модели работы платформы. Использование устаревших синхронных подходов или некорректная обработка события ПередЗакрытием гарантированно приведет к неудобству пользователей при выходе из программы.

Для разработки надежного решения всегда следуйте правилам:

← На главную