Как правильно передать реквизиты из подчиненной управляемой формы в основную в 1С 8.3

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Переход платформы 1С:Предприятие 8.3 на управляемые формы кардинально изменил подход к разработке интерфейса. Одним из самых серьезных вызовов для разработчиков, привыкших к обычным формам, стал полный отказ от модальности. Если раньше задача получения значения из дочерней формы решалась в одну строку через метод ОткрытьМодально(), то в асинхронном мире управляемых форм этот подход больше не работает. Давайте подробно разберем, почему так произошло, и проанализируем самые надежные, технически правильные и элегантные способы передачи данных из подчиненной управляемой формы в основную.

Почему платформа 1С отказалась от модальности?

Прежде чем перейти к практике, выясним причину этого архитектурного решения. Отказ от модальных окон — это не прихоть разработчиков платформы, а жесткая технологическая необходимость. Модальные окна полностью блокируют поток выполнения кода и интерфейс на уровне операционной системы. Это делает невозможной полноценную работу системы в веб-клиенте (современные браузеры блокируют такие окна в целях безопасности) и на мобильных платформах (iOS и Android просто не поддерживают концепцию перекрывающих друг друга блокирующих окон).

Переход на асинхронную работу с использованием обратных вызовов (колбэков) позволил сделать код конфигураций универсальным. Теперь одна и та же форма без изменений работает в тонком клиенте на Windows, в веб-браузере и на мобильном устройстве.

---

Способ 1. Современный стандарт: Использование ОписанияОповещения и закрытие с возвратом результата

Этот способ является наиболее гибким, правильным и рекомендованным фирмой 1С. Мы открываем форму немодально, но блокируем интерфейс приложения (или только владельца), имитируя модальный режим для пользователя. При этом исполнение кода на клиенте не останавливается. Передача данных обратно осуществляется в процедуре-обработчике оповещения, которая вызывается автоматически в момент закрытия подчиненной формы.

Рассмотрим по шагам, как это реализовать.

Шаг 1. Настройка родительской (основной) формы.

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


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

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

Шаг 2. Настройка подчиненной (дочерней) формы.

В подчиненной форме нам достаточно обработать закрытие формы и передать данные в метод Закрыть().


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    // Принимаем исходные параметры, если они были переданы
    Если Параметры.Свойство("ТекущееЗначение") Тогда
        РеквизитПодчиненнойФормы = Параметры.ТекущееЗначение;
    КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура КнопкаОКВыполнить(Команда)
    
    // Формируем структуру с возвращаемыми значениями
    СтруктураРезультата = Новый Структура;
    СтруктураРезультата.Вставить("ЗначениеРеквизита", РеквизитПодчиненнойФормы);
    
    // Закрываем форму и передаем структуру в метод Закрыть
    // Платформа автоматически передаст эту структуру в процедуру "ОбработатьРезультатПодчиненнойФормы" основного окна
    Закрыть(СтруктураРезультата);
    
КонецПроцедуры

Важный нюанс: Обратите внимание, что выполнение кода в процедуре ОткрытьПодчиненнуюФорму после вызова метода ОткрытьФорму продолжится мгновенно! Не пытайтесь писать код обработки результата сразу после вызова открытия формы — весь этот код должен находиться строго в экспортной процедуре оповещения.

---

Способ 2. Использование штатного механизма выбора (ОповеститьОВыборе)

Этот способ идеально подходит для случаев, когда дочерняя форма выступает в роли формы подбора, справочника или формы выбора конкретного значения. Он является наиболее органичным для платформы 1С.

Проанализируем этот механизм. При открытии дочерней формы мы явно передаем ссылку на основную форму в качестве ее владельца. Это дает дочерней форме возможность отправить стандартное оповещение о выборе.

Реализация в родительской форме:

Нам необходимо переопределить стандартное событие формы ОбработкаВыбора.


&НаКлиенте
Процедура ОткрытьФормуВыбора(Команда)
    // Открываем подчиненную форму, обязательно назначая владельцем "ЭтотОбъект"
    ОткрытьФорму("ВнешняяОбработка.МояОбработка.Форма.Форма2", , ЭтотОбъект);
КонецПроцедуры

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

Реализация в подчиненной форме:

В подчиненной форме при нажатии кнопки выбора мы вызываем метод ОповеститьОВыборе().


&НаКлиенте
Процедура КнопкаВыбратьВыполнить(Команда)
    
    СтруктураВыбора = Новый Структура;
    СтруктураВыбора.Вставить("ВыбранноеЗначение", РеквизитПодчиненнойФормы);
    
    // Отправляем данные владельцу. Метод автоматически закрывает подчиненную форму,
    // если она была открыта в режиме выбора.
    ОповеститьОВыборе(СтруктураВыбора);
    
КонецПроцедуры

---

Способ 3. Прямой вызов экспортных методов основной формы

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

Посмотрим на пример реализации такого взаимодействия.

В родительской форме:

Создаем экспортную клиентскую процедуру, которая будет принимать порции данных.


&НаКлиенте
Процедура ПринятьДанныеИзДочернейФормы(ПереданныеДанные) Экспорт
    
    // Обрабатываем полученные данные по мере их поступления
    Объект.ПолеВвода = ПереданныеДанные;
    
КонецПроцедуры

В подчиненной форме:

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


&НаКлиенте
Процедура РеквизитПодчиненнойФормыПриИзменении(Элемент)
    
    // Проверяем, что владелец формы задан и доступен
    Если ЗначениеЗаполнено(ВладелецФормы) Тогда
        Попытка
            // Вызываем экспортную процедуру родительской формы напрямую
            ВладелецФормы.ПринятьДанныеИзДочернейФормы(РеквизитПодчиненнойФормы);
        Исключение
            // Обработка исключения на случай, если у владельца нет такого метода
            ЗаписьЖурналаРегистрации("Ошибка передачи данных", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
        КонецПопытки;
    КонецЕсли;
    
КонецПроцедуры

Важное ограничение: Вызываемый метод родительской формы обязательно должен иметь директиву компиляции &НаКлиенте и ключевое слово Экспорт. Вызов серверных методов формы извне напрямую платформой запрещен в целях безопасности.

---

Способ 4. Глобальный механизм оповещений (Оповестить / ОбработкаОповещения)

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

В подчиненной форме:

Мы посылаем широковещательное сообщение в систему с помощью метода Оповестить().


&НаКлиенте
Процедура КнопкаСохранитьИОповестить(Команда)
    
    ПараметрыСобытия = Новый Структура("НовоеЗначение", РеквизитПодчиненнойФормы);
    
    // Посылаем глобальный сигнал всем открытым формам системы
    Оповестить("ИзменениеДанныхФормы2", ПараметрыСобытия, ЭтотОбъект);
    
    Закрыть();
    
КонецПроцедуры

В родительской форме (или любой другой заинтересованной форме):

Прописываем стандартный обработчик события ОбработкаОповещения.


&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
    
    // Проверяем, наше ли это событие
    Если ИмяСобытия = "ИзменениеДанныхФормы2" Тогда
        Объект.РеквизитОсновнойФормы = Параметр.НовоеЗначение;
    КонецЕсли;
    
КонецПроцедуры

---

Способ 5. Передача тяжелых данных через Временное хранилище

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

В этом случае на помощь приходит ВременноеХранилище.

Разберем алгоритм по шагам.

Шаг 1. В родительской форме (на сервере):

Мы помещаем пустой объект или базовую таблицу во временное хранилище, привязывая адрес к уникальному идентификатору основной формы. Это гарантирует автоматическую очистку памяти после закрытия родительской формы.


&НаСервере
Функция ПодготовитьАдресХранилища()
    Таблица = Новый ТаблицаЗначений;
    // Определяем колонки нашей таблицы
    Таблица.Колонки.Добавить("Товар");
    Таблица.Колонки.Добавить("Количество");
    
    // Помещаем во временное хранилище с привязкой к УникальномуИдентификатору формы
    Возврат ПоместитьВоВременноеХранилище(Таблица, УникальныйИдентификатор);
КонецФункции

Передаем полученный адрес в подчиненную форму при ее открытии через параметры.

Шаг 2. В подчиненной форме (на сервере):

Считываем данные по адресу, модифицируем их и записываем обратно.


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    // Сохраняем адрес хранилища в реквизит формы для последующего использования
    АдресХранилища = Параметры.АдресХранилища;
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьИСохранитьДанныеНаСервере()
    // Получаем таблицу из хранилища
    ТаблицаДанных = ПолучитьИзВременногоХранилища(АдресХранилища);
    
    // Модифицируем ее (например, заполняем данными из формы)
    НоваяСтрока = ТаблицаДанных.Добавить();
    НоваяСтрока.Товар = Справочники.Номенклатура.НайтиПоКоду("00001");
    НоваяСтрока.Количество = 10;
    
    // Помещаем обновленную таблицу обратно по тому же адресу
    ПоместитьВоВременноеХранилище(ТаблицаДанных, АдресХранилища);
КонецПроцедуры

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

Шаг 3. В родительской форме (в обработчике закрытия):

Забираем обновленные данные из хранилища на сервере.


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

&НаСервере
Процедура ПолучитьДанныеИзХранилищаНаСервере()
    // Получаем измененную таблицу по известному адресу
    ТаблицаРезультата = ПолучитьИзВременногоХранилища(АдресХранилища);
    // Заполняем реквизиты основной формы полученными данными
    Объект.Товары.Загрузить(ТаблицаРезультата);
КонецПроцедуры

---

Сводная таблица сравнения методов

Для того чтобы вам было проще выбрать оптимальный вариант решения задачи, сравним все рассмотренные подходы:

  1. ОписаниеОповещения (Способ 1)
    • Плюсы: Универсальность, независимость форм друг от друга, гибкость передачи любых типов данных.
    • Минусы: Требует написания дополнительных экспортных процедур.
    • Рекомендуемая сфера: Любые нетиповые подчиненные формы ввода данных.
  2. ОповеститьОВыборе (Способ 2)
    • Плюсы: Минимальное количество кода, стандартное платформенное поведение.
    • Минусы: Форма жестко ориентирована на режим выбора одного значения.
    • Рекомендуемая сфера: Формы подбора, списки выбора элементов.
  3. Прямой вызов через ВладелецФормы (Способ 3)
    • Плюсы: Возможность мгновенной («живой») передачи данных без закрытия подчиненной формы.
    • Минусы: Тесная связанность форм (дочерняя форма должна точно знать структуру методов родителя).
    • Рекомендуемая сфера: Интерактивные панели настроек, калькуляторы.
  4. Глобальные оповещения (Способ 4)
    • Плюсы: Полная развязанность кода, возможность оповестить множество окон одновременно.
    • Минусы: Сложность отладки при большом количестве перекрестных оповещений в системе.
    • Рекомендуемая сфера: Синхронизация списков и журналов при изменении объектов.
  5. Временное хранилище (Способ 5)
    • Плюсы: Высокая производительность при передаче больших массивов информации (таблиц значений).
    • Минусы: Необходимость серверных вызовов для работы с хранилищем.
    • Рекомендуемая сфера: Загрузка данных из внешних файлов (Excel, XML), групповая обработка строк.

Подведем итог: Использование прямой привязки вида ВладелецФормы.Реквизит = "Значение", упомянутой автором темы на форуме, хотя технически и работоспособно, не является хорошим тоном разработки. Оно создает жесткую архитектурную связь между формами и усложняет дальнейшее сопровождение кода. Применяйте асинхронный механизм ОписаниеОповещения или штатную ОбработкаВыбора — это гарантирует стабильную, быструю и отказоустойчивую работу ваших конфигураций в любых средах и на любых устройствах!

← На главную