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