Модальные окна в 1С 8.3: диалоги, формы и асинхронные вызовы
Содержание
Модальное окно в 1С 8.3 выводят тремя способами. Простой диалог — функциями Вопрос(), Предупреждение(), ВвестиЧисло(). Свою форму — через ОткрытьФормуМодально(). Но в новых конфигурациях эти вызовы запрещены свойством «Режим использования модальности», и вместо них работают ПоказатьВопрос(), ПоказатьПредупреждение() и ОткрытьФорму() с обработчиком закрытия, а на платформе 8.3.18 и выше — ВопросАсинх() с оператором Ждать.
Какой вариант писать, решает одна настройка конфигурации. Если модальность разрешена, старый код работает как есть. Если нет, при вызове Вопрос() пользователь увидит ошибку Использование модальных окон в данном режиме запрещено, и код надо переделать на оповещения. Все примеры рассчитаны на клиентский модуль управляемой формы.
Модальное и немодальное окно: в чём разница для кода
Модальное окно блокирует работу программы, пока пользователь его не закроет. Для разработчика главное другое: модальный вызов останавливает выполнение кода. Строка после Вопрос() выполнится только тогда, когда пользователь нажмёт кнопку.
Немодальный (блокирующий) вызов ведёт себя иначе:
- 🔹 окно открывается и тоже не даёт работать с формой-владельцем;
- 🔹 но код идёт дальше сразу, не дожидаясь ответа;
- 🔹 результат приходит позже, в отдельную процедуру-обработчик.
Почему фирма 1С ушла от модальности. Браузеры не умеют останавливать скрипт в ожидании ответа так, как это делает толстый клиент. Веб-клиент и мобильная платформа с модальными окнами работали плохо: всплывающие окна блокировались, интерфейс зависал. Начиная с платформы 8.3.3 появилась возможность писать код без модальных вызовов, и типовые конфигурации перешли на него.
| Что сравниваем | Модальный вызов | Вызов с оповещением |
|---|---|---|
| Выполнение кода | Ждёт ответа | Идёт дальше сразу |
| Где получить ответ | В той же строке | В процедуре-обработчике |
| Веб-клиент, мобильный клиент | Работает с ограничениями или запрещён | Работает |
| Режим модальности «Не использовать» | Ошибка при вызове | Работает |
| Объём кода | Короче | Длиннее на одну процедуру |
Где разрешают и запрещают модальность в конфигурации
Прежде чем писать код, проверьте настройку. Откройте Конфигуратор, выделите корень дерева конфигурации и откройте его свойства. В группе «Совместимость» есть свойство «Режим использования модальности» с тремя значениями:
- Использовать — модальные функции работают как в 8.2. Подходит для старых самописных баз на толстом клиенте.
- Использовать с предупреждением — вызовы работают, но платформа пишет предупреждение в окно сообщений. Удобно на этапе переделки кода.
- Не использовать — любой модальный вызов вызывает исключение. Так настроены современные типовые конфигурации.
Рядом находится свойство «Режим использования синхронных вызовов расширений платформы и внешних компонент». Оно про другое: про работу с файлами, буфером и внешними компонентами. С модальными окнами его путают, но настраивается оно отдельно.
В типовой конфигурации на поддержке это свойство менять нельзя без снятия с поддержки корня. Пишите код под режим «Не использовать», а не подстраивайте конфигурацию под старый код. После включения модальности в типовой базе следующее обновление может вернуть настройку обратно.
Найти все модальные вызовы в своих модулях помогает проверка конфигурации. В Конфигураторе запустите Конфигурация → Проверка конфигурации и включите флаг поиска использования модальности. Платформа выдаст список строк, которые придётся переписать.
Встроенные диалоги: вопрос, предупреждение, ввод значения
Для простых сообщений своя форма не нужна. Платформа даёт готовые диалоги, и у каждого есть пара: модальная функция и немодальная процедура.
| Задача | Модальный вызов | Без модальности | 8.3.18+ |
|---|---|---|---|
| Сообщение с кнопкой «ОК» | Предупреждение() | ПоказатьПредупреждение() | ПредупреждениеАсинх() |
| Вопрос «Да/Нет» | Вопрос() | ПоказатьВопрос() | ВопросАсинх() |
| Ввод значения любого типа | ВвестиЗначение() | ПоказатьВводЗначения() | ВвестиЗначениеАсинх() |
| Ввод числа | ВвестиЧисло() | ПоказатьВводЧисла() | ВвестиЧислоАсинх() |
| Ввод строки | ВвестиСтроку() | ПоказатьВводСтроки() | ВвестиСтрокуАсинх() |
| Ввод даты | ВвестиДату() | ПоказатьВводДаты() | ВвестиДатуАсинх() |
Все эти вызовы работают только на клиенте. В процедуре с директивой &НаСервере вывести диалог нельзя.
Модальный вопрос — классический вариант
Так код выглядел в 8.2 и так работает при режиме «Использовать»:
&НаКлиенте
Процедура Удалить(Команда)
Ответ = Вопрос("Удалить выбранную строку?", РежимДиалогаВопрос.ДаНет);
Если Ответ = КодВозвратаДиалога.Да Тогда
УдалитьСтроку();
КонецЕсли;
КонецПроцедуры
Тот же вопрос без модальности
Вызов делится на две процедуры. Первая задаёт вопрос, вторая получает ответ:
&НаКлиенте
Процедура Удалить(Команда)
Оповещение = Новый ОписаниеОповещения("УдалитьЗавершение", ЭтотОбъект);
ПоказатьВопрос(Оповещение, "Удалить выбранную строку?", РежимДиалогаВопрос.ДаНет);
КонецПроцедуры
&НаКлиенте
Процедура УдалитьЗавершение(Ответ, ДополнительныеПараметры) Экспорт
Если Ответ = КодВозвратаДиалога.Да Тогда
УдалитьСтроку();
КонецЕсли;
КонецПроцедуры
Три детали, без которых код не заработает:
- ✅ имя в
ОписаниеОповещениясовпадает с именем процедуры-обработчика буква в букву; - ✅ обработчик объявлен со словом
Экспорт; - ✅ у обработчика ровно два параметра: результат и дополнительные параметры.
Предупреждение и ввод числа
Для ПоказатьПредупреждение() обработчик можно не передавать — если после окна ничего делать не нужно, первым параметром ставят Неопределено:
ПоказатьПредупреждение(, "Документ проведён.", 10);
Третий параметр — таймаут в секундах: через 10 секунд окно закроется само.
Ввод числа с обработкой отказа:
&НаКлиенте
Процедура ЗадатьСкидку(Команда)
Оповещение = Новый ОписаниеОповещения("ЗадатьСкидкуЗавершение", ЭтотОбъект);
ПоказатьВводЧисла(Оповещение, 0, "Скидка, %", 5, 2);
КонецПроцедуры
&НаКлиенте
Процедура ЗадатьСкидкуЗавершение(Число, ДополнительныеПараметры) Экспорт
Если Число = Неопределено Тогда
Возврат; // пользователь нажал «Отмена»
КонецЕсли;
Объект.ПроцентСкидки = Число;
КонецПроцедуры
Если нужно просто сообщить о результате и ничего не ждать, окно вообще не обязательно. ПоказатьОповещениеПользователя() выводит всплывающее сообщение в углу экрана, а Сообщить() — строку в панель сообщений формы. Пользователь не тратит клик на «ОК».
Как открыть свою форму в модальном режиме
Когда стандартного диалога мало — нужен выбор из списка, несколько полей, таблица — открывают свою форму. Здесь тоже два пути.
ОткрытьФормуМодально — если модальность разрешена
&НаКлиенте
Процедура ВыбратьПериод(Команда)
Параметры = Новый Структура("НачалоПериода", Объект.Дата);
Результат = ОткрытьФормуМодально("Обработка.Загрузка.Форма.ВыборПериода", Параметры, ЭтотОбъект);
Если Результат <> Неопределено Тогда
Объект.Дата = Результат;
КонецЕсли;
КонецПроцедуры
Функция возвращает то, что форма передала при закрытии. Если пользователь закрыл окно крестиком — вернётся Неопределено.
В обычном приложении (толстый клиент, обычные формы) действовали так:
Форма = ПолучитьФорму("Обработка.Загрузка.Форма.ВыборПериода");
Результат = Форма.ОткрытьМодально();
Этот вариант относится только к обычным формам. В управляемом приложении используйте ОткрытьФормуМодально() или код с оповещением ниже.
ОткрытьФорму с режимом блокировки — современный путь
ОткрытьФорму() принимает обработчик закрытия и режим открытия окна. Порядок параметров: имя формы, параметры, владелец, уникальность, окно, навигационная ссылка, описание оповещения о закрытии, режим открытия окна.
&НаКлиенте
Процедура ВыбратьПериод(Команда)
Параметры = Новый Структура("НачалоПериода", Объект.Дата);
Оповещение = Новый ОписаниеОповещения("ВыбратьПериодЗавершение", ЭтотОбъект);
ОткрытьФорму("Обработка.Загрузка.Форма.ВыборПериода", Параметры, ЭтотОбъект,,,,
Оповещение, РежимОткрытияОкнаФормы.БлокироватьОкноВладельца);
КонецПроцедуры
&НаКлиенте
Процедура ВыбратьПериодЗавершение(Результат, ДополнительныеПараметры) Экспорт
Если Результат <> Неопределено Тогда
Объект.Дата = Результат;
КонецЕсли;
КонецПроцедуры
Режим блокировки задаётся значением системного перечисления РежимОткрытияОкнаФормы:
| Значение | Что блокирует | Когда брать |
|---|---|---|
Независимый | Ничего | Обычное открытие формы |
БлокироватьОкноВладельца | Только форму, указанную владельцем | Выбор значения для конкретного документа |
БлокироватьВесьИнтерфейс | Всё приложение | Ближе всего к старой модальности: ввод пароля, обязательное подтверждение |
Тот же режим можно задать в самой форме, в её свойстве «Режим открытия окна». Тогда форма будет блокирующей при любом способе открытия.
Для БлокироватьОкноВладельца обязательно передайте владельца третьим параметром (ЭтотОбъект или элемент формы). Без владельца блокировать нечего, и форма откроется как независимая.
Асинхронные функции: Асинх и Ждать на платформе 8.3.18+
Код с ОписаниеОповещения надёжен, но читается тяжело: логика рвётся на куски. С версии платформы 8.3.18 в языке появились асинхронные процедуры. Они позволяют писать последовательно, как в модальном коде, но без блокировки.
&НаКлиенте
Асинх Процедура Удалить(Команда)
Ответ = Ждать ВопросАсинх("Удалить выбранную строку?", РежимДиалогаВопрос.ДаНет);
Если Ответ = КодВозвратаДиалога.Да Тогда
УдалитьСтроку();
КонецЕсли;
КонецПроцедуры
Как это устроено:
- Ключевое слово
АсинхпередПроцедураилиФункцияделает её асинхронной. ВопросАсинх()сразу возвращает объектОбещание, а не ответ.- Оператор
Ждатьприостанавливает именно эту процедуру до ответа пользователя. Интерфейс при этом не замирает. Ждатьможно писать только внутри процедуры или функции сАсинх.
Условие одно, но жёсткое: платформа 8.3.18 и выше и режим совместимости конфигурации не ниже 8.3.18. Если в свойствах конфигурации стоит совместимость 8.3.14, слово Асинх вызовет синтаксическую ошибку. Тогда остаётся вариант с ОписаниеОповещения.
Асинхронная функция тоже возвращает Обещание. Чтобы получить её значение в другой асинхронной процедуре, пишут Результат = Ждать МояФункцияАсинх();.
Как вернуть данные из модального окна
Открыть форму — половина задачи. Вторая половина — получить из неё результат.
Передать значение при закрытии
В открытой форме значение отдают методом Закрыть() с параметром:
&НаКлиенте
Процедура Выбрать(Команда)
Закрыть(НачалоПериода);
КонецПроцедуры
Значение попадает:
- 📌 в результат функции
ОткрытьФормуМодально()— при модальном открытии; - 📌 в первый параметр обработчика из
ОписаниеОповещения— при открытии черезОткрытьФорму().
Нужно вернуть несколько значений — соберите их в Структура:
Результат = Новый Структура;
Результат.Вставить("Начало", НачалоПериода);
Результат.Вставить("Окончание", КонецПериода);
Закрыть(Результат);
Передать данные в форму при открытии
Входные данные передают структурой во втором параметре ОткрытьФорму(). В открываемой форме создают параметр с тем же именем (вкладка «Параметры» в редакторе формы) и читают его в ПриСозданииНаСервере:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
НачалоПериода = Параметры.НачалоПериода;
КонецПроцедуры
Протащить контекст в обработчик
Иногда обработчику нужно знать больше, чем ответ: какую строку удалять, какой документ менять. Для этого третий параметр ОписаниеОповещения — дополнительные параметры:
Доп = Новый Структура("ИдентификаторСтроки", Элементы.Товары.ТекущаяСтрока);
Оповещение = Новый ОписаниеОповещения("УдалитьЗавершение", ЭтотОбъект, Доп);
В обработчике это значение придёт вторым параметром: ДополнительныеПараметры.ИдентификаторСтроки. Так не приходится хранить временные данные в реквизитах формы.
Частые ошибки при выводе модальных окон
Ошибка 1. Код после ПоказатьВопрос() выполняется до ответа. Это главная ловушка при переходе со старого кода. Всё, что зависит от ответа, переносите в обработчик. Строки после ПоказатьВопрос() выполнятся мгновенно, ещё до того, как пользователь увидит окно.
Ошибка 2. Обработчик не вызывается. Почти всегда причина одна из трёх: процедура без Экспорт, опечатка в имени, обработчик лежит не в том модуле, который передан вторым параметром ОписаниеОповещения. Для общего модуля вторым параметром передают сам модуль, и он должен быть клиентским.
Ошибка 3. Вопрос в ПередЗакрытием или ПередЗаписью. Обработчик события ждать ответа не может: событие закончится раньше. Рабочая схема — отменить действие, задать вопрос, а в обработчике повторить действие с флагом:
&НаКлиенте
Перем ЗакрытиеПодтверждено;
&НаКлиенте
Процедура ПередЗакрытием(Отказ, ЗавершениеРаботы, ТекстПредупреждения, СтандартнаяОбработка)
Если ЗакрытиеПодтверждено = Истина Или ЗавершениеРаботы Тогда
Возврат;
КонецЕсли;
Отказ = Истина;
Оповещение = Новый ОписаниеОповещения("ПередЗакрытиемЗавершение", ЭтотОбъект);
ПоказатьВопрос(Оповещение, "Закрыть форму без сохранения?", РежимДиалогаВопрос.ДаНет);
КонецПроцедуры
&НаКлиенте
Процедура ПередЗакрытиемЗавершение(Ответ, ДополнительныеПараметры) Экспорт
Если Ответ = КодВозвратаДиалога.Да Тогда
ЗакрытиеПодтверждено = Истина;
Закрыть();
КонецЕсли;
КонецПроцедуры
При завершении работы программы задавать вопросы нельзя — для этого и проверяется параметр ЗавершениеРаботы. Текст для пользователя в этом случае пишут в параметр ТекстПредупреждения.
Ошибка 4. Диалог на сервере. Вопрос() и ПоказатьВопрос() в процедуре &НаСервере не работают. Нужен ответ пользователя посреди серверной логики — разбейте её: сервер проверяет и возвращает признак, клиент спрашивает, затем снова вызывает сервер.
Ошибка 5. Ждать вне Асинх-процедуры. Компилятор выдаст ошибку синтаксиса. Слово Асинх нужно и у самой процедуры, и у всех функций, которые внутри используют Ждать.