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

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

При разработке интерфейсов на управляемых формах в 1С:Предприятии 8 часто возникает классическая задача: из основной формы (например, документа отгрузки) открыть вспомогательную форму (например, для проверки количества или сканирования штрихкодов), выполнить там определенные действия и вернуть измененные данные обратно. Начинающие разработчики часто сталкиваются со сложностями при попытке передать таблицы «туда и обратно» из-за жесткого разделения контекста выполнения на клиент и сервер. В качестве альтернативного интерфейсного решения можно встроить одну форму непосредственно в другую; для этого пригодится инструмент для открытия управляемых форм внутри других форм.

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

В чем заключается основная сложность? Клиент-серверный барьер

Давайте проанализируем типичную ошибку, с которой сталкиваются разработчики при попытке напрямую перенести строки таблицы. Рассмотрим код, который часто пытаются написать интуитивно:


// Ошибка! Этот код не будет работать
ЭтаФорма.ВладелецФормы.Объект.ОтгружаемыеТовары.Загрузить(Товары.Выгрузить);

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

Для обхода этого ограничения в платформе 1С предусмотрены специализированные механизмы. Ниже мы разберем лучшие практики решения этой задачи.

---

Способ 1. Использование описания оповещения о закрытии (Архитектурно верный подход)

С точки зрения хорошего тона программирования и объектно-ориентированного дизайна, форма проверки должна выступать в роли «черного ящика»: она получает исходные данные, выполняет свою задачу и возвращает готовый структурированный результат, не вмешиваясь во внутреннее устройство вызвавшей ее формы. Чтобы не разрабатывать подобные вспомогательные интерфейсы с нуля, для простых сценариев подойдёт готовая универсальная форма для ввода значений и заполнения структур.

Для этого мы используем объект ОписаниеОповещения. Рассмотрим, как это реализовать по шагам.

Шаг 1. На стороне основной формы документа

Создадим процедуру открытия вспомогательной формы, передав в метод ОткрытьФорму() описание оповещения:


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

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

Шаг 2. На стороне вспомогательной формы проверки

После завершения работы пользователя (например, по нажатию кнопки «ОК» или «Применить») мы собираем измененные данные и передаем их в метод Закрыть():


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

---

Способ 2. Прямое копирование данных на клиенте через КопироватьДанныеФормы

Если структуры табличных частей в обеих формах абсолютно идентичны и нам не требуется сложная бизнес-логика обработки результатов, мы можем перенести данные напрямую на клиенте. Для этого используется глобальный метод КопироватьДанныеФормы().

Этот метод уникален тем, что он доступен как на сервере, так и на клиенте. Он позволяет обойти ограничение методов Загрузить/Выгрузить.

Посмотрим на пример реализации в модуле вспомогательной формы проверки при закрытии:


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

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

---

Способ 3. Передача больших таблиц через Временное хранилище (Оптимально по производительности)

Представим ситуацию, когда табличная часть документа содержит тысячи строк. Передача таких массивов данных «туда и обратно» через клиентский контекст может вызвать ощутимые задержки в интерфейсе (так как происходит сериализация больших объемов XML/JSON при передаче между клиентом и сервером).

В этом случае наиболее производительным решением будет использование временного хранилища. Разберем этот паттерн подробно:

  1. Перед открытием формы проверки на основной форме мы помещаем таблицу во временное хранилище и получаем его уникальный адрес.
  2. Передаем этот адрес в качестве параметра во вспомогательную форму.
  3. Вспомогательная форма на сервере считывает данные, позволяет пользователю их отредактировать, сохраняет изменения обратно в то же хранилище и закрывается.
  4. Основная форма забирает обновленные данные из временного хранилища на сервере.

Рассмотрим программную реализацию этой схемы.

Код в основной форме документа:


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

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

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

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

Код во вспомогательной форме проверки:


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

&НаКлиенте
Процедура СохранитьИзмененияИЗакрыть(Команда)
    
    СохранитьДанныеВХранилищеНаСервере();
    Закрыть("Успешно");
    
КонецПроцедуры

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

---

Способ 4. Стандартный механизм выбора (ОповеститьОВыборе и ОбработкаВыбора)

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

Для этого при открытии формы в качестве владельца мы передаем не саму форму, а конкретный элемент управления (например, таблицу формы Элементы.ОтгружаемыеТовары). Тогда платформа автоматически перенаправит возвращенное значение в событие ОбработкаВыбора.

Шаг 1. Открытие формы из основной формы:


&НаКлиенте
Процедура НачатьПодбор(Команда)
    
    ПараметрыФормы = Новый Структура;
    // Передаем элемент управления в качестве владельца
    ОткрытьФорму(
        "Документ.АМ_ОтгрузкаТоваров.Форма.ФормаПроверкаКоличестваТоваровВДокументе", 
        ПараметрыФормы, 
        Элементы.ОтгружаемыеТовары
    );
    
КонецПроцедуры

Шаг 2. Возврат значения из формы проверки:


&НаКлиенте
Процедура ЗавершитьВыбор(Команда)
    
    МассивВыбранныхСтрок = Новый Массив;
    // Наполняем массив результатом работы...
    
    // Платформа сама закроет эту форму и вызовет событие ОбработкаВыбора у владельца
    ОповеститьОВыборе(МассивВыбранныхСтрок);
    
КонецПроцедуры

Шаг 3. Обработка результата в основной форме:
Для таблицы ОтгружаемыеТовары в основной форме определяем событие ОбработкаВыбора:


&НаКлиенте
Процедура ОтгружаемыеТоварыОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
    
    // Обрабатываем ВыбранноеЗначение (наш массив или структура)
    // ...
    
КонецПроцедуры

---

Способ 5. Глобальные оповещения через Оповестить() и ОбработкаОповещения

Иногда связь между формами является слабой (loose coupling). Например, форма проверки открыта в отдельном окне, и мы хотим, чтобы при изменении данных в ней обновлялись сразу несколько других открытых окон (например, динамические списки документов или отчеты), при этом формы не связаны отношением «владелец-подчиненный».

В этом случае используется глобальный клиентский механизм оповещений.

В форме проверки при сохранении данных:


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

В основной форме (или любой другой форме, желающей принять сигнал):
В свойствах формы определяем обработчик события ОбработкаОповещения:


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

---

Резюме: какой способ выбрать?

Подведем итоги нашего анализа и выберем оптимальные сценарии применения каждого метода:

← На главную