При разработке интерфейсов на управляемых формах в 1С:Предприятии 8 часто возникает классическая задача: из основной формы (например, документа отгрузки) открыть вспомогательную форму (например, для проверки количества или сканирования штрихкодов), выполнить там определенные действия и вернуть измененные данные обратно. Начинающие разработчики часто сталкиваются со сложностями при попытке передать таблицы «туда и обратно» из-за жесткого разделения контекста выполнения на клиент и сервер. В качестве альтернативного интерфейсного решения можно встроить одну форму непосредственно в другую; для этого пригодится инструмент для открытия управляемых форм внутри других форм.
В этой статье мы подробно разберем, как правильно организовать передачу данных между формами, проанализируем ограничения платформы и рассмотрим пять надежных способов реализации — от простых оповещений до оптимизированной работы через временное хранилище.
Давайте проанализируем типичную ошибку, с которой сталкиваются разработчики при попытке напрямую перенести строки таблицы. Рассмотрим код, который часто пытаются написать интуитивно:
// Ошибка! Этот код не будет работать
ЭтаФорма.ВладелецФормы.Объект.ОтгружаемыеТовары.Загрузить(Товары.Выгрузить);
Выясним причину, почему данный подход вызывает ошибку платформы. Дело в том, что свойство ВладелецФормы доступно исключительно в клиентском контексте. В то же время методы Выгрузить() и Загрузить() для табличных частей и таблиц значений являются чисто серверными. Попытка вызвать их на клиенте приведет к ошибке компиляции. На сервере же у нас нет прямого доступа к свойству ВладелецФормы текущего клиентского сеанса.
Для обхода этого ограничения в платформе 1С предусмотрены специализированные механизмы. Ниже мы разберем лучшие практики решения этой задачи.
---
С точки зрения хорошего тона программирования и объектно-ориентированного дизайна, форма проверки должна выступать в роли «черного ящика»: она получает исходные данные, выполняет свою задачу и возвращает готовый структурированный результат, не вмешиваясь во внутреннее устройство вызвавшей ее формы. Чтобы не разрабатывать подобные вспомогательные интерфейсы с нуля, для простых сценариев подойдёт готовая универсальная форма для ввода значений и заполнения структур.
Для этого мы используем объект ОписаниеОповещения. Рассмотрим, как это реализовать по шагам.
Шаг 1. На стороне основной формы документа
Создадим процедуру открытия вспомогательной формы, передав в метод ОткрытьФорму() описание оповещения:
&НаКлиенте
Процедура ОткрытьФормуПроверкиКоличества(Команда)
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("ДокументАМОтгрузкаТоваров", Объект.Ссылка);
// Создаем описание оповещения, указывая имя процедуры-обработчика и модуль, где она находится
ОповещениеОЗакрытии = Новый ОписаниеОповещения("ПослеПроверкиКоличества", ЭтотОбъект);
// Открываем форму проверки, передавая оповещение последним параметром
ОткрытьФорму(
"Документ.АМ_ОтгрузкаТоваров.Форма.ФормаПроверкаКоличестваТоваровВДокументе",
ПараметрыФормы,
ЭтотОбъект,
,,,,
ОповещениеОЗакрытии
);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПроверкиКоличества(РезультатПроверки, ДополнительныеПараметры) Экспорт
// Проверяем, что пользователь не просто закрыл форму крестиком, а подтвердил изменения
Если РезультатПроверки = Неопределено Тогда
Возврат;
КонецЕсли;
// Обрабатываем полученный результат (например, структуру с массивом измененных строк)
ПрименитьРезультатыПроверкиНаСервере(РезультатПроверки);
КонецПроцедуры
Шаг 2. На стороне вспомогательной формы проверки
После завершения работы пользователя (например, по нажатию кнопки «ОК» или «Применить») мы собираем измененные данные и передаем их в метод Закрыть():
&НаКлиенте
Процедура ПодтвердитьВводИЗакрыть(Команда)
// Формируем структуру с результатами работы формы
РезультатПроверки = Новый Структура;
РезультатПроверки.Вставить("СтатусПроверки", Истина);
РезультатПроверки.Вставить("ДанныеТоваров", ПолучитьТаблицуРезультатовНаСервере());
// Закрываем форму, передавая результат в основную форму
Закрыть(РезультатПроверки);
КонецПроцедуры
---
Если структуры табличных частей в обеих формах абсолютно идентичны и нам не требуется сложная бизнес-логика обработки результатов, мы можем перенести данные напрямую на клиенте. Для этого используется глобальный метод КопироватьДанныеФормы().
Этот метод уникален тем, что он доступен как на сервере, так и на клиенте. Он позволяет обойти ограничение методов Загрузить/Выгрузить.
Посмотрим на пример реализации в модуле вспомогательной формы проверки при закрытии:
&НаКлиенте
Процедура ЗавершитьРедактированиеИЗаписать(Команда)
// Проверяем, задан ли владелец формы (основная форма)
Если ВладелецФормы <> Неопределено Тогда
// Напрямую копируем коллекцию строк из формы проверки в форму-владелец
КопироватьДанныеФормы(ЭтаФорма.Товары, ВладелецФормы.Объект.ОтгружаемыеТовары);
// Оповещаем основную форму о том, что данные изменены
ВладелецФормы.Модифицированность = Истина;
КонецЕсли;
Закрыть();
КонецПроцедуры
Важное примечание: Этот способ очень прост в реализации, однако он создает сильную связность (tight coupling) между формами. Вспомогательная форма должна точно знать внутреннюю структуру реквизитов владельца, что усложняет повторное использование этой формы в других документах.
---
Представим ситуацию, когда табличная часть документа содержит тысячи строк. Передача таких массивов данных «туда и обратно» через клиентский контекст может вызвать ощутимые задержки в интерфейсе (так как происходит сериализация больших объемов XML/JSON при передаче между клиентом и сервером).
В этом случае наиболее производительным решением будет использование временного хранилища. Разберем этот паттерн подробно:
Рассмотрим программную реализацию этой схемы.
Код в основной форме документа:
&НаКлиенте
Процедура ОткрытьФормуСХранилищем(Команда)
// Помещаем таблицу во временное хранилище на сервере
АдресХранилища = ПодготовитьДанныеВХранилищеНаСервере();
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("АдресДанных", АдресХранилища);
ОповещениеОЗакрытии = Новый ОписаниеОповещения("ПослеЗакрытияФормыПроверки", ЭтотОбъект, АдресХранилища);
ОткрытьФорму(
"Документ.АМ_ОтгрузкаТоваров.Форма.ФормаПроверкаКоличестваТоваровВДокументе",
ПараметрыФормы,
ЭтотОбъект,
,,,,
ОповещениеОЗакрытии
);
КонецПроцедуры
&НаСервере
Функция ПодготовитьДанныеВХранилищеНаСервере()
// Сериализуем табличную часть в таблицу значений для помещения в хранилище
ТаблицаДляХранения = Объект.ОтгружаемыеТовары.Выгрузить();
// Привязываем адрес к УникальномуИдентификатору текущей формы для автоматической очистки памяти
Возврат ПоместитьВоВременноеХранилище(ТаблицаДляХранения, ЭтаФорма.УникальныйИдентификатор);
КонецФункции
&НаКлиенте
Процедура ПослеЗакрытияФормыПроверки(Результат, АдресХранилища) Экспорт
// Если форма была успешно закрыта с подтверждением
Если Результат = "Успешно" Тогда
ПолучитьДанныеИзХранилищаНаСервере(АдресХранилища);
КонецЕсли;
КонецПроцедуры
&НаСервере
Процедура ПолучитьДанныеИзХранилищаНаСервере(АдресХранилища)
ТаблицаИзХранилища = ПолучитьИзВременногоХранилища(АдресХранилища);
Если ТипЗнч(ТаблицаИзХранилища) = Тип("ТаблицаЗначений") Тогда
Объект.ОтгружаемыеТовары.Загрузить(ТаблицаИзХранилища);
Модифицированность = Истина;
КонецЕсли;
КонецПроцедуры
Код во вспомогательной форме проверки:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Получаем адрес хранилища из параметров
АдресДанных = Параметры.АдресДанных;
// Загружаем данные в локальную таблицу формы проверки
ТаблицаДанных = ПолучитьИзВременногоХранилища(АдресДанных);
Если ТаблицаДанных <> Неопределено Тогда
Товары.Загрузить(ТаблицаДанных);
КонецЕсли;
КонецПроцедуры
&НаКлиенте
Процедура СохранитьИзмененияИЗакрыть(Команда)
СохранитьДанныеВХранилищеНаСервере();
Закрыть("Успешно");
КонецПроцедуры
&НаСервере
Процедура СохранитьДанныеВХранилищеНаСервере()
// Выгружаем отредактированные данные
ТаблицаДляСохранения = Товары.Выгрузить();
// Перезаписываем данные по тому же адресу в хранилище
ПоместитьВоВременноеХранилище(ТаблицаДляСохранения, Параметры.АдресДанных);
КонецПроцедуры
---
Если вспомогательная форма по своей логике является формой подбора или выбора значений, максимально лаконичным решением будет использование встроенного в платформу 1С паттерна выбора.
Для этого при открытии формы в качестве владельца мы передаем не саму форму, а конкретный элемент управления (например, таблицу формы Элементы.ОтгружаемыеТовары). Тогда платформа автоматически перенаправит возвращенное значение в событие ОбработкаВыбора.
Шаг 1. Открытие формы из основной формы:
&НаКлиенте
Процедура НачатьПодбор(Команда)
ПараметрыФормы = Новый Структура;
// Передаем элемент управления в качестве владельца
ОткрытьФорму(
"Документ.АМ_ОтгрузкаТоваров.Форма.ФормаПроверкаКоличестваТоваровВДокументе",
ПараметрыФормы,
Элементы.ОтгружаемыеТовары
);
КонецПроцедуры
Шаг 2. Возврат значения из формы проверки:
&НаКлиенте
Процедура ЗавершитьВыбор(Команда)
МассивВыбранныхСтрок = Новый Массив;
// Наполняем массив результатом работы...
// Платформа сама закроет эту форму и вызовет событие ОбработкаВыбора у владельца
ОповеститьОВыборе(МассивВыбранныхСтрок);
КонецПроцедуры
Шаг 3. Обработка результата в основной форме:
Для таблицы ОтгружаемыеТовары в основной форме определяем событие ОбработкаВыбора:
&НаКлиенте
Процедура ОтгружаемыеТоварыОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
// Обрабатываем ВыбранноеЗначение (наш массив или структура)
// ...
КонецПроцедуры
---
Иногда связь между формами является слабой (loose coupling). Например, форма проверки открыта в отдельном окне, и мы хотим, чтобы при изменении данных в ней обновлялись сразу несколько других открытых окон (например, динамические списки документов или отчеты), при этом формы не связаны отношением «владелец-подчиненный».
В этом случае используется глобальный клиентский механизм оповещений.
В форме проверки при сохранении данных:
&НаКлиенте
Процедура ИзменитьДанныеИОповестить(Команда)
ПараметрыОповещения = Новый Структура;
ПараметрыОповещения.Вставить("ДокументСсылка", ОбъектСсылка);
ПараметрыОповещения.Вставить("Статус", "Проверено");
// Отправляем глобальный сигнал всем открытым формам текущего сеанса
Оповестить("ДокументПроверен", ПараметрыОповещения, ЭтотОбъект);
Закрыть();
КонецПроцедуры
В основной форме (или любой другой форме, желающей принять сигнал):
В свойствах формы определяем обработчик события ОбработкаОповещения:
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
// Проверяем, наше ли это событие
Если ИмяСобытия = "ДокументПроверен" Тогда
// Проверяем, к нашему ли документу относится событие
Если Параметр.ДокументСсылка = Объект.Ссылка Тогда
// Обновляем данные на форме
ПрочитатьДанныеНаСервере();
КонецЕсли;
КонецЕсли;
КонецПроцедуры
---
Подведем итоги нашего анализа и выберем оптимальные сценарии применения каждого метода: