При разработке в среде 1С:Предприятие 8.3 на управляемых формах начинающие разработчики часто сталкиваются с проблемой взаимодействия между родительской (вызывающей) и дочерней (вызываемой) формами. Типичный сценарий: из дочерней формы необходимо запустить процедуру формирования печатной формы, которая описана в модуле родительской формы.
Попытка напрямую передать форму-владельца в качестве параметра на сервер приводит к возникновению критической ошибки: «Ошибка отображения типов: отсутствует отображение для типа формаклиентскогоприложения». Выясним причину этой ошибки, проанализируем решение автора темы и рассмотрим архитектурно правильные альтернативные варианты реализации этой задачи в соответствии со стандартами разработки фирмы «1С».
В платформе 1С управляемые формы разделены на клиентскую и серверную части. Объект типа УправляемаяФорма (или ФормаКлиентскогоПриложения на стороне клиента) физически существует только на клиенте. Его невозможно сериализовать (преобразовать в поток байтов) и передать на сервер.
Когда мы пытаемся передать переменную ВладелецФормы (которая содержит ссылку на родительскую форму) в процедуру или функцию, выполняющуюся на сервере (например, в метод формирования макета табличного документа), платформа пытается отправить этот объект на сервер. Так как у типа ФормаКлиентскогоПриложения нет серверного отображения, система закономерно сообщает об ошибке.
Автор темы обошла это ограничение, передав на сервер не саму форму, а её ключевой реквизит — Объект (который на клиенте имеет тип ДанныеФормыСтруктура). Этот тип данных специально оптимизирован для быстрой передачи между клиентом и сервером.
Рассмотрим код вызова, к которому пришел автор в итоге:
&НаКлиенте
Процедура ПечатьПриложения(Команда)
вф = ВладелецФормы.Объект;
Печать(вф);
КонецПроцедуры
И код самой процедуры печати на сервере:
Функция Печать(ВладелецФ)
ТабДок = Новый ТабличныйДокумент;
Макет = Документы.ЧекККМ.ПолучитьМакет("Макет");
ОбластьПриложение = Макет.ПолучитьОбласть("Приложение");
// Получение адреса организации
ОбластьПриложение.Параметры.АдресГостиницы = УправлениеКонтактнойИнформацией.КонтактнаяИнформацияОбъекта(
ВладелецФ.Организация,
Справочники.ВидыКонтактнойИнформации.ЮрАдресОрганизации,
ТекущаяДатаСеанса(),
Истина
);
// Заполнение параметров из переданного объекта ДанныеФормыСтруктура
ОбластьПриложение.Параметры.номерчека = ВладелецФ.Номер;
ОбластьПриложение.Параметры.датачека = Формат(ВладелецФ.Дата, "ДФ='dd ММММ yyyy'");
ОбластьПриложение.Параметры.Адрес = Строка(ВладелецФ.Адрес);
ТабДок.Вывести(ОбластьПриложение);
// Вывод табличной части
ОбластьСтрокаТ = Макет.ПолучитьОбласть("СтрокаТаблицы");
Для Каждого стр Из ВладелецФ.Товары Цикл
ОбластьСтрокаТ.Параметры.сч = стр.НомерСтроки;
ОбластьСтрокаТ.Параметры.вид = стр.Номенклатура;
ОбластьСтрокаТ.Параметры.колво = стр.КоличествоУпаковок;
ОбластьСтрокаТ.Параметры.цена = стр.Цена;
ОбластьСтрокаТ.Параметры.сумма = стр.Сумма;
ТабДок.Вывести(ОбластьСтрокаТ);
КонецЦикла;
// Вывод подвала
ОбластьПодвал = Макет.ПолучитьОбласть("Подвал");
ОбластьПодвал.Параметры.ИтогСумма = ВладелецФ.СуммаДокумента;
ОбластьПодвал.Параметры.Пользователь = ВладелецФ.Кассир;
ТабДок.Вывести(ОбластьПодвал);
ТабДок.ОтображатьСетку = Ложь;
ТабДок.Показать("Приложение");
КонецФункции
Такой подход работоспособен, однако он создает сильную связанность (Tight Coupling) между формами. Дочерняя форма жестко зависит от структуры реквизитов родительской формы (ожидается наличие реквизитов Организация, Номер, Товары и т.д.). Если эту же форму потребуется вызвать из другого места системы, код выдаст ошибку. Кроме того, размещение тяжелой логики генерации макетов и запросов внутри модуля формы нарушает стандарты разработки.
Проанализируем три альтернативных способа решения этой задачи, которые считаются более профессиональными и гибкими.
---
Согласно стандартам разработки «1С» и БСП, любая печать должна выполняться на сервере через модуль менеджера объекта (документа, справочника). Форма должна лишь инициировать этот процесс, передавая ссылку на объект базы данных.
Разберем реализацию этого метода по шагам:
ЧекККМ создадим экспортную функцию печати, которая принимает массив ссылок на документы:
Функция СформироватьПечатнуюФормуПриложение(МассивСсылок) Экспорт
ТабДок = Новый ТабличныйДокумент;
Макет = Документы.ЧекККМ.ПолучитьМакет("Макет");
Для Каждого СсылкаНаДокумент Из МассивСсылок Цикл
ОбластьПриложение = Макет.ПолучитьОбласть("Приложение");
// Получаем данные непосредственно из базы данных по ссылке, а не из формы
ОбластьПриложение.Параметры.АдресГостиницы = УправлениеКонтактнойИнформацией.КонтактнаяИнформацияОбъекта(
СсылкаНаДокумент.Организация,
Справочники.ВидыКонтактнойИнформации.ЮрАдресОрганизации
);
ОбластьПриложение.Параметры.номерчека = СсылкаНаДокумент.Номер;
ОбластьПриложение.Параметры.датачека = Формат(СсылкаНаДокумент.Дата, "ДФ='dd ММММ yyyy'");
ТабДок.Вывести(ОбластьПриложение);
// Логика вывода строк...
КонецЦикла;
Возврат ТабДок;
EndФункции
ДокументСсылка. На форме создадим команду печати:
&НаКлиенте
Процедура ПечатьПриложения(Команда)
Если Не ЗначениеЗаполнено(ДокументСсылка) Тогда
ПоказатьПредупреждение(, "Документ еще не записан или ссылка пустая!");
Возврат;
КонецЕсли;
ТабДок = СформироватьНаСервере(ДокументСсылка);
ТабДок.Показать("Приложение");
КонецПроцедуры
&НаСервереБезКонтекста
Функция СформироватьНаСервере(Ссылка)
МассивСсылок = Новый Массив;
МассивСсылок.Добавить(Ссылка);
Возврат Документы.ЧекККМ.СформироватьПечатнуюФормуПриложение(МассивСсылок);
КонецФункции
Этот способ идеален, так как полностью разделяет интерфейс формы и бизнес-логику печати.
---
Если дочерняя форма не должна сама заниматься печатью, а должна лишь сообщить родительской форме о необходимости выполнить это действие, мы можем применить механизм глобальных оповещений платформы 1С. Это позволяет построить архитектуру со слабой связью (Loose Coupling).
Посмотрим на пример реализации:
&НаКлиенте
Процедура ПечатьПриложения(Команда)
// Передаем уникальное имя события и, при необходимости, структуру с параметрами
ПараметрыСобытия = Новый Структура("Ссылка", Объект.Ссылка);
Оповестить("ВыполнитьПечатьПриложенияЧекККМ", ПараметрыСобытия, ЭтаФорма);
КонецПроцедуры
ОбработкаОповещения (задается в свойствах самой формы):
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
// Проверяем, наше ли это событие
Если ИмяСобытия = "ВыполнитьПечатьПриложенияЧекККМ" Тогда
// Здесь мы можем безопасно запустить локальную процедуру печати родительской формы
ВыполнитьЛокальнуюПечатьЧека(Параметр.Ссылка);
КонецЕсли;
КонецПроцедуры
При таком подходе формы ничего не знают о внутреннем устройстве друг друга. Дочерняя форма просто «кричит» в систему о своем желании распечатать документ, а родительская форма «слушает» этот сигнал и отрабатывает его.
---
Если мы точно уверены, что дочерняя форма всегда вызывается из конкретной родительской формы, мы можем вызвать экспортный метод владельца напрямую. Однако здесь действует важнейшее правило платформы 1С: напрямую из внешнего кода формы можно вызвать только те процедуры и функции, которые объявлены с директивой &НаКлиенте Экспорт.
&НаКлиенте
Процедура ВыполнитьПечатьПриложенияЭкспорт() Экспорт
// Вызываем локальный серверный метод печати формы
ТабДок = СформироватьПечатьНаСервере();
ТабДок.Показать();
КонецПроцедуры
&НаКлиенте
Процедура ПечатьПриложения(Команда)
// Проверяем, что владелец формы действительно является управляемой формой
Если ТипЗнч(ВладелецФормы) = Тип("УправляемаяФорма") Тогда
Попытка
// Прямой вызов клиентского экспортного метода родительской формы
ВладелецФормы.ВыполнитьПечатьПриложенияЭкспорт();
Исключение
ЗаписьЖурналаРегистрации("Печать", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
ПоказатьПредупреждение(, "Не удалось вызвать метод печати у формы-владельца!");
КонецПопытки;
КонецЕсли;
КонецПроцедуры
Этот способ прост в реализации, когда требуется быстро доработать нетиповой функционал без изменения глобальных модулей, но требует обязательной обработки исключений на случай изменения контекста вызова.