Модальные окна в 1С 8.3: диалоги, формы и асинхронные вызовы

· 9 мин

Содержание

Модальное окно в 1С 8.3 выводят тремя способами. Простой диалог — функциями Вопрос(), Предупреждение(), ВвестиЧисло(). Свою форму — через ОткрытьФормуМодально(). Но в новых конфигурациях эти вызовы запрещены свойством «Режим использования модальности», и вместо них работают ПоказатьВопрос(), ПоказатьПредупреждение() и ОткрытьФорму() с обработчиком закрытия, а на платформе 8.3.18 и выше — ВопросАсинх() с оператором Ждать.

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

Модальное и немодальное окно: в чём разница для кода

Модальное окно блокирует работу программы, пока пользователь его не закроет. Для разработчика главное другое: модальный вызов останавливает выполнение кода. Строка после Вопрос() выполнится только тогда, когда пользователь нажмёт кнопку.

Немодальный (блокирующий) вызов ведёт себя иначе:

  • 🔹 окно открывается и тоже не даёт работать с формой-владельцем;
  • 🔹 но код идёт дальше сразу, не дожидаясь ответа;
  • 🔹 результат приходит позже, в отдельную процедуру-обработчик.

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

Что сравниваемМодальный вызовВызов с оповещением
Выполнение кодаЖдёт ответаИдёт дальше сразу
Где получить ответВ той же строкеВ процедуре-обработчике
Веб-клиент, мобильный клиентРаботает с ограничениями или запрещёнРаботает
Режим модальности «Не использовать»Ошибка при вызовеРаботает
Объём кодаКорочеДлиннее на одну процедуру

Где разрешают и запрещают модальность в конфигурации

Прежде чем писать код, проверьте настройку. Откройте Конфигуратор, выделите корень дерева конфигурации и откройте его свойства. В группе «Совместимость» есть свойство «Режим использования модальности» с тремя значениями:

  1. Использовать — модальные функции работают как в 8.2. Подходит для старых самописных баз на толстом клиенте.
  2. Использовать с предупреждением — вызовы работают, но платформа пишет предупреждение в окно сообщений. Удобно на этапе переделки кода.
  3. Не использовать — любой модальный вызов вызывает исключение. Так настроены современные типовые конфигурации.

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

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

Найти все модальные вызовы в своих модулях помогает проверка конфигурации. В Конфигураторе запустите Конфигурация → Проверка конфигурации и включите флаг поиска использования модальности. Платформа выдаст список строк, которые придётся переписать.

Встроенные диалоги: вопрос, предупреждение, ввод значения

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

ЗадачаМодальный вызовБез модальности8.3.18+
Сообщение с кнопкой «ОК»Предупреждение()ПоказатьПредупреждение()ПредупреждениеАсинх()
Вопрос «Да/Нет»Вопрос()ПоказатьВопрос()ВопросАсинх()
Ввод значения любого типаВвестиЗначение()ПоказатьВводЗначения()ВвестиЗначениеАсинх()
Ввод числаВвестиЧисло()ПоказатьВводЧисла()ВвестиЧислоАсинх()
Ввод строкиВвестиСтроку()ПоказатьВводСтроки()ВвестиСтрокуАсинх()
Ввод датыВвестиДату()ПоказатьВводДаты()ВвестиДатуАсинх()

Все эти вызовы работают только на клиенте. В процедуре с директивой &НаСервере вывести диалог нельзя.

Модальный вопрос — классический вариант

Так код выглядел в 8.2 и так работает при режиме «Использовать»:

&НаКлиенте
Процедура Удалить(Команда)
	Ответ = Вопрос("Удалить выбранную строку?", РежимДиалогаВопрос.ДаНет);
	Если Ответ = КодВозвратаДиалога.Да Тогда
		УдалитьСтроку();
	КонецЕсли;
КонецПроцедуры

Тот же вопрос без модальности

Вызов делится на две процедуры. Первая задаёт вопрос, вторая получает ответ:

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

&НаКлиенте
Процедура УдалитьЗавершение(Ответ, ДополнительныеПараметры) Экспорт
	Если Ответ = КодВозвратаДиалога.Да Тогда
		УдалитьСтроку();
	КонецЕсли;
КонецПроцедуры

Три детали, без которых код не заработает:

  • ✅ имя в ОписаниеОповещения совпадает с именем процедуры-обработчика буква в букву;
  • ✅ обработчик объявлен со словом Экспорт;
  • ✅ у обработчика ровно два параметра: результат и дополнительные параметры.

Предупреждение и ввод числа

Для ПоказатьПредупреждение() обработчик можно не передавать — если после окна ничего делать не нужно, первым параметром ставят Неопределено:

ПоказатьПредупреждение(, "Документ проведён.", 10);

Третий параметр — таймаут в секундах: через 10 секунд окно закроется само.

Ввод числа с обработкой отказа:

&НаКлиенте
Процедура ЗадатьСкидку(Команда)
	Оповещение = Новый ОписаниеОповещения("ЗадатьСкидкуЗавершение", ЭтотОбъект);
	ПоказатьВводЧисла(Оповещение, 0, "Скидка, %", 5, 2);
КонецПроцедуры

&НаКлиенте
Процедура ЗадатьСкидкуЗавершение(Число, ДополнительныеПараметры) Экспорт
	Если Число = Неопределено Тогда
		Возврат; // пользователь нажал «Отмена»
	КонецЕсли;
	Объект.ПроцентСкидки = Число;
КонецПроцедуры

Если нужно просто сообщить о результате и ничего не ждать, окно вообще не обязательно. ПоказатьОповещениеПользователя() выводит всплывающее сообщение в углу экрана, а Сообщить() — строку в панель сообщений формы. Пользователь не тратит клик на «ОК».

Как открыть свою форму в модальном режиме

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

ОткрытьФормуМодально — если модальность разрешена

&НаКлиенте
Процедура ВыбратьПериод(Команда)
	Параметры = Новый Структура("НачалоПериода", Объект.Дата);
	Результат = ОткрытьФормуМодально("Обработка.Загрузка.Форма.ВыборПериода", Параметры, ЭтотОбъект);
	Если Результат <> Неопределено Тогда
		Объект.Дата = Результат;
	КонецЕсли;
КонецПроцедуры

Функция возвращает то, что форма передала при закрытии. Если пользователь закрыл окно крестиком — вернётся Неопределено.

В обычном приложении (толстый клиент, обычные формы) действовали так:

Форма = ПолучитьФорму("Обработка.Загрузка.Форма.ВыборПериода");
Результат = Форма.ОткрытьМодально();

Этот вариант относится только к обычным формам. В управляемом приложении используйте ОткрытьФормуМодально() или код с оповещением ниже.

ОткрытьФорму с режимом блокировки — современный путь

ОткрытьФорму() принимает обработчик закрытия и режим открытия окна. Порядок параметров: имя формы, параметры, владелец, уникальность, окно, навигационная ссылка, описание оповещения о закрытии, режим открытия окна.

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

&НаКлиенте
Процедура ВыбратьПериодЗавершение(Результат, ДополнительныеПараметры) Экспорт
	Если Результат <> Неопределено Тогда
		Объект.Дата = Результат;
	КонецЕсли;
КонецПроцедуры

Режим блокировки задаётся значением системного перечисления РежимОткрытияОкнаФормы:

ЗначениеЧто блокируетКогда брать
НезависимыйНичегоОбычное открытие формы
БлокироватьОкноВладельцаТолько форму, указанную владельцемВыбор значения для конкретного документа
БлокироватьВесьИнтерфейсВсё приложениеБлиже всего к старой модальности: ввод пароля, обязательное подтверждение

Тот же режим можно задать в самой форме, в её свойстве «Режим открытия окна». Тогда форма будет блокирующей при любом способе открытия.

Для БлокироватьОкноВладельца обязательно передайте владельца третьим параметром (ЭтотОбъект или элемент формы). Без владельца блокировать нечего, и форма откроется как независимая.

Асинхронные функции: Асинх и Ждать на платформе 8.3.18+

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

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

Как это устроено:

  1. Ключевое слово Асинх перед Процедура или Функция делает её асинхронной.
  2. ВопросАсинх() сразу возвращает объект Обещание, а не ответ.
  3. Оператор Ждать приостанавливает именно эту процедуру до ответа пользователя. Интерфейс при этом не замирает.
  4. Ждать можно писать только внутри процедуры или функции с Асинх.

Условие одно, но жёсткое: платформа 8.3.18 и выше и режим совместимости конфигурации не ниже 8.3.18. Если в свойствах конфигурации стоит совместимость 8.3.14, слово Асинх вызовет синтаксическую ошибку. Тогда остаётся вариант с ОписаниеОповещения.

Асинхронная функция тоже возвращает Обещание. Чтобы получить её значение в другой асинхронной процедуре, пишут Результат = Ждать МояФункцияАсинх();.

Какой способ вывода окна выбрать?

Как вернуть данные из модального окна

Открыть форму — половина задачи. Вторая половина — получить из неё результат.

Передать значение при закрытии

В открытой форме значение отдают методом Закрыть() с параметром:

&НаКлиенте
Процедура Выбрать(Команда)
	Закрыть(НачалоПериода);
КонецПроцедуры

Значение попадает:

  • 📌 в результат функции ОткрытьФормуМодально() — при модальном открытии;
  • 📌 в первый параметр обработчика из ОписаниеОповещения — при открытии через ОткрытьФорму().

Нужно вернуть несколько значений — соберите их в Структура:

Результат = Новый Структура;
Результат.Вставить("Начало", НачалоПериода);
Результат.Вставить("Окончание", КонецПериода);
Закрыть(Результат);

Передать данные в форму при открытии

Входные данные передают структурой во втором параметре ОткрытьФорму(). В открываемой форме создают параметр с тем же именем (вкладка «Параметры» в редакторе формы) и читают его в ПриСозданииНаСервере:

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	НачалоПериода = Параметры.НачалоПериода;
КонецПроцедуры

Протащить контекст в обработчик

Иногда обработчику нужно знать больше, чем ответ: какую строку удалять, какой документ менять. Для этого третий параметр ОписаниеОповещения — дополнительные параметры:

Доп = Новый Структура("ИдентификаторСтроки", Элементы.Товары.ТекущаяСтрока);
Оповещение = Новый ОписаниеОповещения("УдалитьЗавершение", ЭтотОбъект, Доп);

В обработчике это значение придёт вторым параметром: ДополнительныеПараметры.ИдентификаторСтроки. Так не приходится хранить временные данные в реквизитах формы.

Частые ошибки при выводе модальных окон

Ошибка 1. Код после ПоказатьВопрос() выполняется до ответа. Это главная ловушка при переходе со старого кода. Всё, что зависит от ответа, переносите в обработчик. Строки после ПоказатьВопрос() выполнятся мгновенно, ещё до того, как пользователь увидит окно.

Ошибка 2. Обработчик не вызывается. Почти всегда причина одна из трёх: процедура без Экспорт, опечатка в имени, обработчик лежит не в том модуле, который передан вторым параметром ОписаниеОповещения. Для общего модуля вторым параметром передают сам модуль, и он должен быть клиентским.

Ошибка 3. Вопрос в ПередЗакрытием или ПередЗаписью. Обработчик события ждать ответа не может: событие закончится раньше. Рабочая схема — отменить действие, задать вопрос, а в обработчике повторить действие с флагом:

&НаКлиенте
Перем ЗакрытиеПодтверждено;

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

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

При завершении работы программы задавать вопросы нельзя — для этого и проверяется параметр ЗавершениеРаботы. Текст для пользователя в этом случае пишут в параметр ТекстПредупреждения.

Ошибка 4. Диалог на сервере. Вопрос() и ПоказатьВопрос() в процедуре &НаСервере не работают. Нужен ответ пользователя посреди серверной логики — разбейте её: сервер проверяет и возвращает признак, клиент спрашивает, затем снова вызывает сервер.

Ошибка 5. Ждать вне Асинх-процедуры. Компилятор выдаст ошибку синтаксиса. Слово Асинх нужно и у самой процедуры, и у всех функций, которые внутри используют Ждать.

Можно ли вывести модальное окно из фонового задания?
Нет. Фоновое задание выполняется на сервере без интерфейса. Результат возвращают через сообщения задания или сохраняют, а окно показывают на клиенте после завершения.
Почему в обработке модальные окна работают, а в конфигурации нет?
Внешняя обработка подчиняется режиму модальности той конфигурации, в которой её открыли. В базе с режимом «Использовать» её код с Вопрос() пройдёт, а в типовой с режимом «Не использовать» — упадёт с ошибкой.
Как сделать окно с собственными кнопками вместо Да/Нет?
Передайте в ПоказатьВопрос() вместо РежимДиалогаВопрос список значений: каждый элемент станет кнопкой, а в обработчик придёт значение нажатой кнопки.
Как закрыть вопрос автоматически, если пользователь отошёл?
Укажите таймаут в секундах. По его истечении окно закроется, и в обработчик придёт КодВозвратаДиалога.Таймаут.
Работает ли ОткрытьФормуМодально() в веб-клиенте?
Только при режиме модальности «Использовать», и то с ограничениями браузера. Для веб-клиента надёжнее ОткрытьФорму() с оповещением о закрытии.