При переходе с обычных форм на управляемые или при разработке в режиме "Такси" программисты часто сталкиваются с проблемой: старый добрый метод ВвестиЗначение или модальное открытие формы внутри цикла Для каждого перестает работать так, как ожидалось. Поскольку современные стандарты 1С требуют использования асинхронных методов, интерфейс не блокируется на время выполнения кода, и цикл "пролетает" до конца, не дожидаясь выбора пользователя. В результате либо открывается только последнее окно, либо данные подставляются некорректно.
В этой статье мы подробно разберем, как правильно организовать процесс, который мы будем называть "псевдоциклом". Мы откажемся от стандартного итератора в пользу цепочки вызовов, где каждое следующее окно открывается только после того, как пользователь сделал выбор в предыдущем.
Рассмотрим ситуацию, описанную автором темы. В коде используется ОткрытьФорму с параметром ОписаниеОповещения. В старых версиях платформы (или при разрешенной модальности) выполнение кода остановилось бы на строке открытия формы. Однако в асинхронном режиме процедура продолжает исполнение. Система отправляет команду на открытие окна и тут же переходит к следующей итерации цикла. В итоге программа пытается открыть десятки окон одновременно, а контекст переменной стр (строка таблицы) постоянно перезаписывается.
Важный вывод: для решения задачи нам нужно научить программу "ждать" ответа пользователя, прежде чем переходить к анализу следующей строки. Сделать это внутри одного цикла Для каждого технически невозможно. Решением станет рекурсивный вызов логики обработки.
Рассмотрим основной и самый эффективный способ. Мы создадим процедуру, которая ищет "проблемную" строку, требующую заполнения, и, если находит ее, открывает форму выбора. Когда форма закрывается, срабатывает процедура оповещения, которая записывает результат и... снова вызывает ту же самую процедуру поиска. Цикл повторяется, пока не закончатся строки с пустой номенклатурой.
Разберем реализацию по шагам:
Проанализируем пример кода:
&НаКлиенте
Процедура НачатьЗаполнениеНоменклатуры(Команда)
// Запускаем процесс обработки с самого начала
ОбработатьСледующуюСтроку();
КонецПроцедуры
&НаКлиенте
Процедура ОбработатьСледующуюСтроку()
СтрокаДляОбработки = Неопределено;
// Ищем первую попавшуюся строку, где номенклатура не заполнена
Для каждого Стр Из Объект.ТаблицаДанных Цикл
Если Стр.Номенклатура.Пустая() Тогда
СтрокаДляОбработки = Стр;
Прервать; // Выходим из цикла поиска, как только нашли одну строку
КонецЕсли;
КонецЦикла;
// Если нашли строку — открываем форму выбора
Если СтрокаДляОбработки <> Неопределено Тогда
ПараметрыФормы = Новый Структура("Наименование", СтрокаДляОбработки.Наименование);
// В ОписаниеОповещения передаем саму строку как дополнительный параметр
Оповещение = Новый ОписаниеОповещения("ЗавершитьВыборНоменклатуры", ЭтотОбъект, СтрокаДляОбработки);
ОткрытьФорму("Обработка.МояОбработка.Форма.ВыборНоменклатуры",
ПараметрыФормы,
ЭтаФорма,
,,,,
Оповещение,
РежимОткрытияОкнаФормы.БлокироватьВесьИнтерфейс);
Иначе
// Если пустых строк больше нет — выводим сообщение об окончании
ПоказатьПредупреждение(, "Все строки успешно обработаны!");
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ЗавершитьВыборНоменклатуры(Результат, СтрокаТаблицы) Экспорт
// Проверяем, что пользователь что-то выбрал, а не просто закрыл окно
Если ЗначениеЗаполнено(Результат) Тогда
СтрокаТаблицы.Номенклатура = Результат;
КонецЕсли;
// КЛЮЧЕВОЙ МОМЕНТ: Снова вызываем поиск следующей строки.
// Это создает эффект цикла, но без блокировки потока.
ОбработатьСледующуюСтроку();
КонецПроцедуры
В этом примере мы видим четкое разделение ответственности: одна процедура только ищет, что нужно сделать, а вторая — применяет изменения. Это позволяет избежать глубокой вложенности кода и делает его читаемым.
Иногда поиск строки в цикле Для каждого на каждой итерации может быть неэффективным (например, если в таблице тысячи строк). В этом случае мы можем заранее сформировать список (массив) индексов тех строк, которые требуют внимания пользователя.
Рассмотрим, как это реализовать:
ОписаниеОповещения.
&НаКлиенте
Процедура ОбработатьМассивСтрок(Команда)
МассивИндексов = Новый Массив;
Для Индекс = 0 По Объект.ТаблицаДанных.Количество() - 1 Цикл
Если Объект.ТаблицаДанных[Индекс].Номенклатура.Пустая() Тогда
МассивИндексов.Добавить(Индекс);
КонецЕсли;
КонецЦикла;
Если МассивИндексов.Количество() > 0 Тогда
ПараметрыПроцесса = Новый Структура;
ПараметрыПроцесса.Вставить("Массив", МассивИндексов);
ПараметрыПроцесса.Вставить("ТекущийШаг", 0);
ВыполнитьШагВыбора(ПараметрыПроцесса);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьШагВыбора(ПараметрыПроцесса)
Если ПараметрыПроцесса.ТекущийШаг >= ПараметрыПроцесса.Массив.Количество() Тогда
Возврат; // Все готово
КонецЕсли;
ИндексСтроки = ПараметрыПроцесса.Массив[ПараметрыПроцесса.ТекущийШаг];
ТекущаяСтрока = Объект.ТаблицаДанных[ИндексСтроки];
Оповещение = Новый ОписаниеОповещения("ПослеВыбораВШаге", ЭтотОбъект, ПараметрыПроцесса);
// Открываем форму...
ОткрытьФорму(... , Оповещение, ...);
КонецПроцедуры
&НаКлиенте
Процедура ПослеВыбораВШаге(Результат, ПараметрыПроцесса) Экспорт
Если Результат <> Неопределено Тогда
ИндексСтроки = ПараметрыПроцесса.Массив[ПараметрыПроцесса.ТекущийШаг];
Объект.ТаблицаДанных[ИндексСтроки].Номенклатура = Результат;
КонецЕсли;
// Переходим к следующему элементу массива
ПараметрыПроцесса.ТекущийШаг = ПараметрыПроцесса.ТекущийШаг + 1;
ВыполнитьШагВыбора(ПараметрыПроцесса);
КонецПроцедуры
Как справедливо отмечено в обсуждении на форуме (сообщение 11), открывать пользователю 50 окон подряд — это плохой тон в дизайне интерфейсов. Это утомляет пользователя и повышает риск ошибок. Рассмотрим более профессиональный подход — групповое заполнение.
Вместо последовательного открытия множества модальных окон, мы можем создать одну промежуточную форму-"мастер":
Этот подход полностью снимает проблему асинхронных циклов и значительно ускоряет работу пользователя.
Разберем несколько критических моментов, которые часто упускают разработчики:
Результат будет иметь значение Неопределено. В коде "псевдоцикла" обязательно нужно предусмотреть вызов следующей итерации даже в этом случае, иначе процесс просто прервется на середине.РежимОткрытияОкнаФормы.БлокироватьВесьИнтерфейс. Если этого не сделать, пользователь сможет нажать на кнопку запуска обработки еще раз, пока первое окно еще открыто, что приведет к непредсказуемому поведению и дублированию процессов.ОписаниеОповещения можно передавать не только индекс или строку, но и целые структуры. Если ваша логика зависит от многих параметров, упакуйте их в Структура и передайте в качестве третьего параметра конструктора оповещения.Использование асинхронных вызовов в 1С поначалу кажется избыточным и сложным, но освоение паттерна "цепочки оповещений" позволяет писать гибкий и надежный код, соответствующий современным стандартам разработки платформы 8.3.