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