При переходе на управляемые формы и отказе от модальности разработчики часто сталкиваются с проблемой: как задать пользователю вопрос внутри цикла? В старых версиях платформы 1С:Предприятие классический метод Вопрос() останавливал выполнение кода, ожидал реакции пользователя и продолжал итерацию цикла. В современном асинхронном (немодальном) мире метод ПоказатьВопрос() работает иначе — он не останавливает поток выполнения кода. В результате весь цикл прокручивается мгновенно, на экране одновременно «выстреливает» множество окон с вопросами, а управление передается в процедуру обработки оповещения уже после того, как цикл завершился.
В этой статье мы подробно разберем два решения этой проблемы: классический рекурсивный обход немодальности, подходящий для любых версий платформы 8.3, и современный элегантный подход с использованием ключевых слов Асинх и Ждать (доступен начиная с платформы 8.3.18). Также мы проанализируем, почему вопросы в цикле считаются плохим тоном в проектировании интерфейсов (UX anti-pattern), и рассмотрим лучшие архитектурные альтернативы.
Рассмотрим типичный ошибочный код, который часто пытаются написать начинающие разработчики:
&НаКлиенте
Процедура СоздатьНоменклатуру(Команда)
Для каждого СтрТЧ Из Объект.ТаблицаEXEL Цикл
Оповещение = Новый ОписаниеОповещения("ПослеЗакрытияВопроса", ЭтаФорма);
// Ошибка! Этот метод не останавливает цикл.
ПоказатьВопрос(Оповещение, "Создать номенклатуру '" + СтрТЧ.Наименование + "'?", РежимДиалогаВопрос.ДаНетОтмена);
КонецЦикла;
КонецПроцедуры
Выполняя этот код, платформа мгновенно пробежит по всем строкам таблицы ТаблицаEXEL, нагенерирует десятки или сотни вызовов диалоговых окон и только потом (когда управление вернется к интерфейсному потоку) попытается отобразить их пользователю. Понять, к какой строке относится каждый ответ, и прервать цикл при нажатии кнопки «Отмена» в такой архитектуре физически невозможно.
---
Если ваша конфигурация должна работать на старых релизах платформы 1С (ниже 8.3.18), единственным рабочим решением является замена стандартного цикла Для каждого на пошаговую рекурсивную процедуру. Мы будем передавать индекс обрабатываемой строки в параметрах оповещения и вызывать процедуру обработки следующей строки только после того, как пользователь ответит на текущий вопрос.
Рассмотрим этот алгоритм по шагам:
ОписаниеОповещения, куда в качестве дополнительных параметров передаем текущий индекс строки.Посмотрим на практический пример реализации:
&НаКлиенте
Процедура СоздатьНоменклатуру(Команда)
// Шаг 1: Запускаем пошаговую обработку с первой строки (индекс 0)
ОбработатьСтрокуТаблицы(0);
КонецПроцедуры
&НаКлиенте
Процедура ОбработатьСтрокуТаблицы(ТекущийИндекс)
// Шаг 2: Проверяем условие окончания "цикла"
Если ТекущийИндекс >= Объект.ТаблицаEXEL.Количество() Тогда
ПоказатьПредупреждение(, "Обработка данных успешно завершена!");
Возврат;
КонецЕсли;
// Получаем текущую строку для вывода информации в вопросе
ТекущаяСтрока = Объект.ТаблицаEXEL[ТекущийИндекс];
// Шаг 3: Подготавливаем параметры для передачи в обработчик ответа
ДополнительныеПараметры = Новый Структура;
ДополнительныеПараметры.Вставить("Индекс", ТекущийИндекс);
Оповещение = Новый ОписаниеОповещения("ПослеЗакрытияВопроса", ЭтотОбъект, ДополнительныеПараметры);
// Шаг 4: Показываем немодальный вопрос
ТекстВопроса = "Создать номенклатуру с наименованием '" + ТекущаяСтрока.Наименование + "'?";
ПоказатьВопрос(Оповещение, ТекстВопроса, РежимДиалогаВопрос.ДаНетОтмена, 0);
КонецПроцедуры
&НаКлиенте
Процедура ПослеЗакрытияВопроса(РезультатВопроса, ДополнительныеПараметры) Экспорт
ТекущийИндекс = ДополнительныеПараметры.Индекс;
ТекущаяСтрока = Объект.ТаблицаEXEL[ТекущийИндекс];
// Шаг 5: Анализируем выбор пользователя
Если РезультатВопроса = КодВозвратаДиалога.Отмена Тогда
// Полностью прекращаем обработку
ПоказатьПредупреждение(, "Выполнение операции прервано пользователем.");
Возврат;
ИначеЕсли РезультатВопроса = КодВозвратаДиалога.Да Тогда
// Выполняем создание номенклатуры на сервере
СоздатьНоменклатуруНаСервере(ТекущаяСтрока.Наименование);
КонецЕсли;
// Переходим к следующей строке таблицы (рекурсивный вызов)
ОбработатьСтрокуТаблицы(ТекущийИндекс + 1);
КонецПроцедуры
Важный нюанс: Обратите внимание, что процедура ПослеЗакрытияВопроса обязательно должна быть объявлена с ключевым словом Экспорт, иначе платформа не сможет вызвать ее в контексте управляемой формы.
---
Если вы работаете на актуальных версиях платформы 1С:Предприятие 8.3, вам доступен великолепный инструмент — асинхронные вызовы на основе обещаний (Promises). С их помощью мы можем вернуть коду привычный, линейный и легко читаемый вид, избавившись от искусственной рекурсии и разделения логики на несколько процедур.
Для реализации этого подхода нам потребуются:
Проанализируем, как выглядит современное решение исходной задачи:
&НаКлиенте
Асинх Процедура СоздатьНоменклатуру(Команда)
Для каждого СтрТЧ Из Объект.ТаблицаEXEL Цикл
ТекстВопроса = "Создать номенклатуру с наименованием '" + СтрТЧ.Наименование + "'?";
// Платформа приостановит выполнение цикла на этой строке,
// вернет управление интерфейсу, а после нажатия кнопки продолжит выполнение.
Ответ = Ждать ВопросАсинх(ТекстВопроса, РежимДиалогаВопрос.ДаНетОтмена, 0);
Если Ответ = КодВозвратаДиалога.Отмена Тогда
// Прерываем цикл мгновенно и лаконично, как в обычных синхронных процедурах
Прервать;
ИначеЕсли Ответ = КодВозвратаДиалога.Да Тогда
СоздатьНоменклатуруНаСервере(СтрТЧ.Наименование);
КонецЕсли;
КонецЦикла;
ПоказатьПредупреждение(, "Обработка завершена!");
КонецПроцедуры
Этот код выглядит невероятно просто, читается сверху вниз и не требует создания сложных вспомогательных структур данных. Настоятельно рекомендуем использовать именно этот метод при работе на современных конфигурациях.
---
При использовании асинхронных вызовов (как через ОписаниеОповещения, так и через Ждать) важно понимать физику процессов в платформе 1С:
Ждать или метода ПоказатьВопрос, текущая процедура приостанавливает работу, но платформа 1С мгновенно возвращает управление в родительский поток, который вызвал эту процедуру. Если вы запустите такой цикл, например, из обработчика события формы ПередЗаписью, форма запишется и закроется еще ДО того, как пользователь успеет ответить хотя бы на первый вопрос.РежимДиалогаВопрос или программно отключать доступность панели кнопок формы.---
С технической точки зрения мы решили задачу. Однако давайте посмотрим на нее глазами конечного пользователя. Представьте, что в файле Excel содержится 500 строк номенклатуры. Если система на каждую строчку будет выводить диалоговое окно, пользователю придется совершить 500 кликов мышкой! Это превращает работу в рутину и повышает риск механической ошибки (пользователь случайно нажмет «Да» там, где нужно было нажать «Нет», или промахнется по кнопке «Отмена»).
Проанализируем более качественные варианты проектирования интерфейса:
Вместо вывода вопросов в цикле выполните предварительный анализ данных на сервере. Создайте в вашей обработке временную таблицу (или используйте существующую табличную часть), куда выведите только те строки, которые вызывают сомнения (например, номенклатура отсутствует в базе данных). Добавьте в эту таблицу колонку с флажком (булево) «Создавать» и колонку с сопоставленной номенклатурой.
Пользователь сможет визуально оценить весь список, быстро расставить галочки (или воспользоваться кнопками «Пометить все» / «Снять пометки»), отредактировать названия прямо в таблице и нажать одну-единственную кнопку «Выполнить». Вся обработка пройдет пакетно, в один клик и без единого всплывающего вопроса.
Обработка больших файлов Excel на клиенте и постоянные вызовы сервера для создания каждой позиции номенклатуры по отдельности (в цикле) создают колоссальную нагрузку на базу данных и сетевой трафик. Правильным архитектурным решением будет передача файла Excel на сервер и запуск Фонового задания. Клиентская часть при этом будет лишь отображать красивый прогресс-бар, а все неоднозначные ситуации или ошибки запишутся в лог-журнал, который пользователь сможет изучить по завершении процесса.