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