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