Сообщения, предупреждения и вопросы в 1С 8.3: какой метод выбрать и как не сломать форму

· 9 мин

Содержание

Чтобы в 1С вывести сообщение пользователю, в простейшем случае хватит одной строки: Сообщить("Документ проведён");. Текст появится в окне сообщений внизу формы. Если нужно окно, которое пользователь обязан закрыть, на клиенте вызывают ПоказатьПредупреждение(), а если нужен ответ «Да/Нет» — ПоказатьВопрос() с обработчиком через ОписаниеОповещения.

Типичная ситуация: разработчик дописывает проверку в документ реализации, ставит старый добрый Предупреждение("Не заполнен склад"), а у бухгалтера в тонком клиенте вылетает ошибка «Использование модальных окон в данном режиме запрещено». Дальше разберём все способы вывода информации в 1С 8.3 и правила, по которым выбирается нужный метод: где выполняется код, нужен ли ответ, надо ли подсветить конкретное поле.

Сообщить, СообщениеПользователю и оповещения: базовые способы вывода информации

У платформы 1С:Предприятие 8.3 есть несколько механизмов. Они отличаются тем, мешают ли работе пользователя и где их можно вызвать.

МетодЧто видит пользовательГде вызываетсяЖдёт реакции
Сообщить()строку в окне сообщений формыклиент и сервернет
СообщениеПользователюстроку с привязкой к полю формыклиент и сервернет
ПоказатьОповещениеПользователя()всплывающее окно в углу экранатолько клиентнет
Состояние()текст и индикатор в строке состояниятолько клиентнет
ПоказатьПредупреждение()окно с кнопкой «ОК»только клиентда, закрыть окно
ПоказатьВопрос()окно с кнопками ответатолько клиентда, выбрать ответ

Сообщить()

Самый короткий способ. Первый параметр — текст, второй (необязательный) — статус сообщения из системного перечисления СтатусСообщения.

Сообщить("Загружено строк: " + Формат(КоличествоСтрок, "ЧГ=0"));
Сообщить("Цена не указана", СтатусСообщения.Внимание);

Числа склеивайте со строкой через Формат(Число, "ЧГ=0") или СтрШаблон(). Иначе 12500 превратится в «12 500» с неразрывным пробелом, и такой текст неудобно копировать.

Объект СообщениеПользователю

Это полноценная замена Сообщить(). Объект умеет привязывать текст к реквизиту: пользователь кликает по сообщению, и курсор встаёт в нужное поле.

Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = НСтр("ru = 'Не заполнен контрагент'");
Сообщение.Поле = "Контрагент";
Сообщение.ПутьКДанным = "Объект";
Сообщение.Сообщить();

Основные свойства:

  • 📝 Текст — сам текст сообщения;
  • 🎯 Поле — имя реквизита или путь к ячейке табличной части;
  • 📂 ПутьКДанным — где лежит реквизит, в форме обычно "Объект";
  • 🔑 КлючДанных — ссылка на объект, если сообщение формируется вне формы;
  • 🪪 ИдентификаторНазначения — уникальный идентификатор формы, в которую нужно доставить сообщение.

Обёртка из БСП

В типовых конфигурациях (БП 3.0, ЗУП 3.1, УТ 11 и других на Библиотеке стандартных подсистем) удобнее процедура ОбщегоНазначения.СообщитьПользователю(). На клиенте — её аналог ОбщегоНазначенияКлиент.СообщитьПользователю(). Внутри она создаёт тот же СообщениеПользователю, а заодно умеет взводить флаг Отказ.

ОбщегоНазначения.СообщитьПользователю(
    НСтр("ru = 'Не указан склад'"), , "Склад", "Объект", Отказ);

Порядок параметров: текст, ключ данных, поле, путь к данным, отказ.

Оповещение и строка состояния

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

ПоказатьОповещениеПользователя("Выгрузка завершена", ,
    "Файл сохранён в папку обмена", БиблиотекаКартинок.Информация);

Состояние() пишет текст в строку состояния и умеет показывать прогресс от 0 до 100. Его вызывают в длинном клиентском цикле, чтобы пользователь видел, что программа не зависла.

Состояние("Обработка строк", Процент, "Строка " + Формат(Номер, "ЧГ=0"));

Диалог с пользователем: предупреждение, вопрос и получение ответа

В платформе 8.3 есть два поколения диалогов: модальные и немодальные. Модальные Предупреждение() и Вопрос() останавливают выполнение кода до ответа. В веб-клиенте и при запрете модальности в свойствах конфигурации они вызывают ошибку.

ПоказатьПредупреждение

&НаКлиенте
Процедура ПроверитьДату(Команда)
    Если Объект.Дата > ТекущаяДата() Тогда
        ПоказатьПредупреждение(, "Дата документа больше текущей", 10, "Проверка");
    КонецЕсли;
КонецПроцедуры

Параметры: описание оповещения о закрытии (можно пропустить), текст, таймаут в секундах, заголовок окна. При таймауте 10 окно закроется само через 10 секунд.

ПоказатьВопрос и обработка ответа

Код после ПоказатьВопрос() выполняется сразу, не дожидаясь ответа. Поэтому всё, что зависит от выбора пользователя, переносят в отдельную экспортную процедуру.

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

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

Наборы кнопок РежимДиалогаВопрос: ДаНет, ДаНетОтмена, ОК, ОКОтмена, ПовторитьОтмена, ПрерватьПовторитьПропустить. Если стандартных подписей мало, вместо режима передают СписокЗначений: значение — что вернётся в Результат, представление — надпись на кнопке.

Кнопки = Новый СписокЗначений;
Кнопки.Добавить("Перезаполнить", "Перезаполнить");
Кнопки.Добавить("Добавить", "Добавить к существующим");
Кнопки.Добавить(КодВозвратаДиалога.Отмена, "Отмена");

Процедура-обработчик обязательно объявляется с ключевым словом Экспорт и находится в модуле, переданном во втором параметре ОписаниеОповещения. Без Экспорт ответ пользователя просто потеряется.

Ввод значения

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

Асинхронный синтаксис: Ждать ВопросАсинх

С платформы 8.3.18 можно писать без цепочек обработчиков. Процедуру помечают словом Асинх, а результат диалога получают оператором Ждать.

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

Для окна без вопроса есть ПредупреждениеАсинх(). Код читается сверху вниз, как в старом модальном стиле, но модальных окон не создаёт.

Асинхронный синтаксис требует, чтобы в свойствах конфигурации режим совместимости был не ниже 8.3.18. В типовых конфигурациях, которые поддерживают более ранние версии, его не встретить — там по-прежнему ОписаниеОповещения.

Сообщения в управляемых формах: клиент, сервер и фоновые задания

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

Сообщения с сервера

Сообщение, сформированное в процедуре &НаСервере, копится и приходит на клиент после возврата из серверного вызова. Пользователь увидит его в окне сообщений той формы, из которой шёл вызов.

Если нужно выбрать форму явно (например, вызов идёт из общего модуля), задают ИдентификаторНазначения:

&НаСервере
Процедура ПроверитьНаСервере()
    Сообщение = Новый СообщениеПользователю;
    Сообщение.Текст = "Остатка не хватает";
    Сообщение.ИдентификаторНазначения = УникальныйИдентификатор;
    Сообщение.Сообщить();
КонецПроцедуры

Привязка к строке табличной части

Чтобы курсор встал в конкретную ячейку, в Поле указывают путь с индексом строки. Индекс начинается с нуля, а НомерСтроки — с единицы, отсюда вычитание.

Для Каждого Строка Из Объект.Товары Цикл
    Если Строка.Количество = 0 Тогда
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = СтрШаблон("Строка %1: не указано количество", Строка.НомерСтроки);
        Сообщение.Поле = СтрШаблон("Товары[%1].Количество", Формат(Строка.НомерСтроки - 1, "ЧГ=0"));
        Сообщение.ПутьКДанным = "Объект";
        Сообщение.Сообщить();
    КонецЕсли;
КонецЦикла;

Для строки с НомерСтроки = 3 поле получится Товары[2].Количество — это третья строка таблицы.

Сообщения в модуле объекта

В модуле объекта документа нет формы. Чтобы сообщение всё равно подсветило реквизит, вызывают Сообщение.УстановитьДанные(ЭтотОбъект) и указывают Поле без пути:

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

Фоновые задания

Сообщения из фонового задания на экран сами не попадают — у задания нет окна. Они накапливаются в задании, а клиент забирает их методом ПолучитьСообщенияПользователю() объекта ФоновоеЗадание. Параметр Истина удаляет полученные сообщения, чтобы при следующем опросе они не дублировались.

Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ИдентификаторЗадания);
Для Каждого Сообщение Из Задание.ПолучитьСообщенияПользователю(Истина) Цикл
    Сообщение.Сообщить();
КонецЦикла;

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

Ошибки и исключения: как сообщить, что пошло не так

Сообщение и исключение решают разные задачи. Сообщение информирует, а исключение прерывает выполнение.

  • 🛑 ВызватьИсключение "Текст"; — останавливает код. Если исключение никто не перехватил, платформа покажет текст в окне ошибки, а транзакция записи откатится.
  • 🧯 Попытка ... Исключение ... КонецПопытки — перехват, когда ошибку нужно обработать, а не показывать «как есть».
  • 📓 ЗаписьЖурналаРегистрации() — техническая подробность для администратора, а не для бухгалтера.
Попытка
    ЗагрузитьФайл(ИмяФайла);
Исключение
    Инфо = ИнформацияОбОшибке();
    ЗаписьЖурналаРегистрации("Загрузка прайса", УровеньЖурналаРегистрации.Ошибка, , ,
        ПодробноеПредставлениеОшибки(Инфо));
    ОбщегоНазначения.СообщитьПользователю(
        "Не удалось загрузить файл: " + КраткоеПредставлениеОшибки(Инфо));
КонецПопытки;

Пользователь видит короткую понятную причину. Полный стек вызовов уходит в журнал регистрации (Администрирование → Обслуживание → Журнал регистрации в БП 3.0).

Пустой блок Исключение КонецПопытки — худший вариант. Ошибка исчезает бесследно, пользователь уверен, что всё загрузилось, а данных нет.

Отказ вместо исключения

В обработчиках ПередЗаписью, ОбработкаПроведения, ОбработкаПроверкиЗаполнения проще установить Отказ = Истина и вывести сообщение. Запись отменится, а пользователь увидит в окне сообщений полный список проблем. Исключение остановилось бы на первой.

Собственная форма вместо стандартного окна

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

Порядок действий:

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

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

В самой форме кнопка выглядит так:

&НаКлиенте
Процедура Перезаполнить(Команда)
    Закрыть("Перезаполнить");
КонецПроцедуры

Режим БлокироватьОкноВладельца не делает окно модальным в смысле платформы. Он лишь закрывает доступ к форме-владельцу, поэтому работает и при запрете модальности, и в веб-клиенте.

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

Частые ошибки при выводе сообщений и как их избежать

Диагностика: выберите симптом

Ещё несколько правил, которые экономят время на сопровождении:

  • 🔤 Тексты для пользователя оборачивайте в НСтр("ru = '...'"). Даже если перевод не планируется, так тексты проще найти и проверить.
  • 🧮 Не показывайте сообщение внутри цикла на 5 000 строк. Соберите ошибки в массив или строку и выведите один итог.
  • 🙊 Не выводите на экран технику: GUID, текст запроса, стек вызовов. Бухгалтеру нужно «что не так и что сделать», остальное — в журнал регистрации.
  • 🪟 Не злоупотребляйте вопросами. Если окно «Вы уверены?» появляется на каждое действие, пользователь перестаёт читать и жмёт «Да» не глядя.
  • 🧹 Перед долгой операцией очищайте старые сообщения методом глобального контекста ОчиститьСообщения(), чтобы прошлые ошибки не путались с новыми.
Чем Сообщить() отличается от СообщениеПользователю?
Сообщить() выводит только текст. СообщениеПользователю дополнительно привязывает его к полю формы или реквизиту объекта, и по клику пользователь попадает в нужное место.
Можно ли показать вопрос из серверной процедуры?
Нет. Окна существуют только на клиенте. Сервер возвращает результат проверки, а клиент по нему решает, задавать ли вопрос.
Как сделать, чтобы предупреждение закрылось само?
Передайте таймаут в секундах третьим параметром ПоказатьПредупреждение(). У ПоказатьВопрос() таймаут тоже есть, а через параметр «КнопкаТаймаута» можно указать, какой ответ считать выбранным по истечении времени.
Почему в обычных формах старый код работает, а в управляемых — нет?
В обычном приложении модальные вызовы разрешены. Управляемые формы в тонком и веб-клиенте рассчитаны на немодальную работу, поэтому код переписывают на ПоказатьВопрос() и аналоги.
Как вывести сообщение без звука и без окна, только в журнал?
Используйте ЗаписьЖурналаРегистрации() с уровнем «Информация» или «Предупреждение». Пользователь ничего не увидит, администратор найдёт запись по имени события.