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