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