Почему не срабатывает обработчик оповещения после закрытия вопроса в произвольной форме 1С?

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

При переходе платформы «1С:Предприятие 8» на асинхронную модель работы (отказ от модальности) разработчики массово столкнулись с необходимостью переписывания стандартных диалогов. Вместо привычного и простого метода Вопрос(), который полностью останавливал выполнение кода на клиенте до получения ответа, нам приходится использовать немодальный метод ПоказатьВопрос() с созданием объекта ОписаниеОповещения.

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


Выясняем причину: почему «теряется» контекст формы?

Давайте проанализируем, как работает асинхронный вызов. Когда мы пишем конструкцию с немодальным вопросом, платформа выполняет код следующим образом:

  1. Создается объект ОписаниеОповещения, в который передается имя процедуры-обработчика и ссылка на модуль, где эта процедура расположена (обычно это текущая форма — ЭтотОбъект или ЭтаФорма).
  2. Вызывается метод ПоказатьВопрос(). Платформа рисует на экране диалоговое окно, но при этом не останавливает выполнение текущего потока кода!
  3. Код, написанный ниже вызова ПоказатьВопрос(), продолжает немедленно исполняться.
  4. Если в этом последующем коде присутствует команда закрытия формы (например, Закрыть()), то форма мгновенно закрывается и уничтожается в оперативной памяти клиентского компьютера.
  5. Спустя время пользователь кликает по кнопке в окне вопроса. Платформа пытается найти контекст вызова — ту самую форму, которая была передана в качестве модуля в ОписаниеОповещения.
  6. Поскольку форма уже закрыта и стерта из памяти, платформа фиксирует отсутствие контекста для вызова процедуры и молча прекращает обработку события. Программа попросту «забывает» зайти в ваш обработчик.

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


Вариант 1. Классическое решение проблемы через ОписаниеОповещения

Рассмотрим по шагам правильную структуру кода. Главное правило асинхронной разработки в 1С: закрытие формы должно происходить только внутри процедуры-обработчика оповещения, после того как ответ пользователя получен и обработан.

Давайте проанализируем правильный пример реализации логики для кнопки на форме:


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

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

При таком подходе форма гарантированно дождется клика пользователя. Ее контекст не уничтожится преждевременно, и платформа без проблем найдет экспортную процедуру ПослеЗакрытияВопросаПерезаписать.


Вариант 2. Обработка вопроса при закрытии формы (событие ПередЗакрытием)

Отдельный и очень частый сценарий — когда вопрос нужно задать пользователю в момент, когда он пытается закрыть форму крестиком или стандартным сочетанием клавиш. Здесь логика усложняется тем, что нам нужно принудительно заблокировать стандартное закрытие формы, задать вопрос и в случае согласия закрыть форму повторно, но уже без вызова диалога.

Рассмотрим, как правильно реализовать этот механизм с использованием переменной модуля:


&НаКлиенте
Перем РазрешитьЗакрытиеФормы; // Переменная для отслеживания состояния закрытия

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

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

Использование переменной модуля — это стандартный паттерн разработки в управляемом приложении 1С. Он позволяет элегантно разорвать бесконечный цикл повторных вызовов события ПередЗакрытием.


Вариант 3. Современное и рекомендуемое решение через Асинх / Ждать (для платформ 8.3.18+)

Начиная с версии платформы 1С **8.3.18**, разработчикам стал доступен великолепный инструмент — асинхронные вызовы на основе обещаний (Promise). Этот механизм избавляет от необходимости плодить множество разрозненных процедур-обработчиков и позволяет писать код линейно, как в старые добрые времена с модальными окнами, сохраняя при этом все преимущества немодального интерфейса.

Давайте посмотрим, как упрощается наша задача при использовании ключевых слов Асинх и Ждать:


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

Преимущества использования конструкции Асинх / Ждать:


Чек-лист для самопроверки: почему обработчик все равно не вызывается?

Если вы применили классическую схему с ОписаниеОповещения, закрытие формы перенесли внутрь обработчика, но программа все равно упорно не заходит в процедуру оповещения, проверьте следующие моменты:

  1. Наличие директивы компиляции: Процедура-обработчик обязательно должна быть объявлена с директивой &НаКлиенте. На сервере интерактивные диалоги выполняться не могут.
  2. Ключевое слово Экспорт: Проверьте, добавлено ли слово Экспорт в сигнатуру процедуры-обработчика. Платформа обращается к методу оповещения динамически, и если метод не является публичным (экспортным), вызов завершится неудачей.
  3. Совпадение имени процедуры: Строка с именем процедуры в конструкторе Новый ОписаниеОповещения("ИмяПроцедуры", ...) должна символ в символ совпадать с реальным именем процедуры в коде. Ошибки в регистре или использование латинских букв вместо русских (и наоборот) — частая причина проблем.
  4. Конфликт имен параметров: Убедитесь, что параметры вашей процедуры-обработчика названы стандартно: (Результат, ДополнительныеПараметры). Избегайте использования служебных и зарезервированных платформой имен переменных (например, не стоит называть второй параметр просто Параметры, если в форме уже есть реквизит или свойство с таким именем, так как это может приводить к конфликту контекстов).

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

← На главную