Как вызвать процедуру печати из другой управляемой формы в 1С и избежать ошибки сериализации

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

При разработке в среде 1С:Предприятие 8.3 на управляемых формах начинающие разработчики часто сталкиваются с проблемой взаимодействия между родительской (вызывающей) и дочерней (вызываемой) формами. Типичный сценарий: из дочерней формы необходимо запустить процедуру формирования печатной формы, которая описана в модуле родительской формы.

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

Выясним причину ошибки: почему возникает сбой?

В платформе 1С управляемые формы разделены на клиентскую и серверную части. Объект типа УправляемаяФорма (или ФормаКлиентскогоПриложения на стороне клиента) физически существует только на клиенте. Его невозможно сериализовать (преобразовать в поток байтов) и передать на сервер.

Когда мы пытаемся передать переменную ВладелецФормы (которая содержит ссылку на родительскую форму) в процедуру или функцию, выполняющуюся на сервере (например, в метод формирования макета табличного документа), платформа пытается отправить этот объект на сервер. Так как у типа ФормаКлиентскогоПриложения нет серверного отображения, система закономерно сообщает об ошибке.

Анализ решения автора темы

Автор темы обошла это ограничение, передав на сервер не саму форму, а её ключевой реквизит — Объект (который на клиенте имеет тип ДанныеФормыСтруктура). Этот тип данных специально оптимизирован для быстрой передачи между клиентом и сервером.

Рассмотрим код вызова, к которому пришел автор в итоге:


&НаКлиенте
Процедура ПечатьПриложения(Команда)
    вф = ВладелецФормы.Объект;
    Печать(вф);
КонецПроцедуры

И код самой процедуры печати на сервере:


Функция Печать(ВладелецФ)
    ТабДок = Новый ТабличныйДокумент;
    Макет = Документы.ЧекККМ.ПолучитьМакет("Макет");
    ОбластьПриложение = Макет.ПолучитьОбласть("Приложение");
    
    // Получение адреса организации
    ОбластьПриложение.Параметры.АдресГостиницы = УправлениеКонтактнойИнформацией.КонтактнаяИнформацияОбъекта(
        ВладелецФ.Организация, 
        Справочники.ВидыКонтактнойИнформации.ЮрАдресОрганизации, 
        ТекущаяДатаСеанса(), 
        Истина
    );
    
    // Заполнение параметров из переданного объекта ДанныеФормыСтруктура
    ОбластьПриложение.Параметры.номерчека = ВладелецФ.Номер;
    ОбластьПриложение.Параметры.датачека = Формат(ВладелецФ.Дата, "ДФ='dd ММММ yyyy'");
    ОбластьПриложение.Параметры.Адрес = Строка(ВладелецФ.Адрес);
    
    ТабДок.Вывести(ОбластьПриложение);
    
    // Вывод табличной части
    ОбластьСтрокаТ = Макет.ПолучитьОбласть("СтрокаТаблицы");
    Для Каждого стр Из ВладелецФ.Товары Цикл
        ОбластьСтрокаТ.Параметры.сч = стр.НомерСтроки;
        ОбластьСтрокаТ.Параметры.вид = стр.Номенклатура;
        ОбластьСтрокаТ.Параметры.колво = стр.КоличествоУпаковок;
        ОбластьСтрокаТ.Параметры.цена = стр.Цена;
        ОбластьСтрокаТ.Параметры.сумма = стр.Сумма;
        ТабДок.Вывести(ОбластьСтрокаТ);
    КонецЦикла;
    
    // Вывод подвала
    ОбластьПодвал = Макет.ПолучитьОбласть("Подвал");                                   
    ОбластьПодвал.Параметры.ИтогСумма = ВладелецФ.СуммаДокумента;
    ОбластьПодвал.Параметры.Пользователь = ВладелецФ.Кассир;
    ТабДок.Вывести(ОбластьПодвал);
    
    ТабДок.ОтображатьСетку = Ложь;
    ТабДок.Показать("Приложение");
КонецФункции

Такой подход работоспособен, однако он создает сильную связанность (Tight Coupling) между формами. Дочерняя форма жестко зависит от структуры реквизитов родительской формы (ожидается наличие реквизитов Организация, Номер, Товары и т.д.). Если эту же форму потребуется вызвать из другого места системы, код выдаст ошибку. Кроме того, размещение тяжелой логики генерации макетов и запросов внутри модуля формы нарушает стандарты разработки.

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

---

Способ 1. Перенос логики печати в модуль менеджера документа (Рекомендуемый стандарт)

Согласно стандартам разработки «1С» и БСП, любая печать должна выполняться на сервере через модуль менеджера объекта (документа, справочника). Форма должна лишь инициировать этот процесс, передавая ссылку на объект базы данных.

Разберем реализацию этого метода по шагам:

  1. В модуле менеджера документа ЧекККМ создадим экспортную функцию печати, которая принимает массив ссылок на документы:

Функция СформироватьПечатнуюФормуПриложение(МассивСсылок) Экспорт
    ТабДок = Новый ТабличныйДокумент;
    Макет = Документы.ЧекККМ.ПолучитьМакет("Макет");
    
    Для Каждого СсылкаНаДокумент Из МассивСсылок Цикл
        ОбластьПриложение = Макет.ПолучитьОбласть("Приложение");
        
        // Получаем данные непосредственно из базы данных по ссылке, а не из формы
        ОбластьПриложение.Параметры.АдресГостиницы = УправлениеКонтактнойИнформацией.КонтактнаяИнформацияОбъекта(
            СсылкаНаДокумент.Организация, 
            Справочники.ВидыКонтактнойИнформации.ЮрАдресОрганизации
        );
        
        ОбластьПриложение.Параметры.номерчека = СсылкаНаДокумент.Номер;
        ОбластьПриложение.Параметры.датачека = Формат(СсылкаНаДокумент.Дата, "ДФ='dd ММММ yyyy'");
        
        ТабДок.Вывести(ОбластьПриложение);
        
        // Логика вывода строк...
    КонецЦикла;
    
    Возврат ТабДок;
EndФункции
  1. В дочерней форме при ее открытии мы должны знать ссылку на документ. Передадим ее в параметрах и сохраним в реквизит формы ДокументСсылка. На форме создадим команду печати:

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

&НаСервереБезКонтекста
Функция СформироватьНаСервере(Ссылка)
    МассивСсылок = Новый Массив;
    МассивСсылок.Добавить(Ссылка);
    Возврат Документы.ЧекККМ.СформироватьПечатнуюФормуПриложение(МассивСсылок);
КонецФункции

Этот способ идеален, так как полностью разделяет интерфейс формы и бизнес-логику печати.

---

Способ 2. Использование механизма оповещений (Слабая связь)

Если дочерняя форма не должна сама заниматься печатью, а должна лишь сообщить родительской форме о необходимости выполнить это действие, мы можем применить механизм глобальных оповещений платформы 1С. Это позволяет построить архитектуру со слабой связью (Loose Coupling).

Посмотрим на пример реализации:

  1. В дочерней форме при нажатии кнопки мы отправляем оповещение всей системе:

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

&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
    // Проверяем, наше ли это событие
    Если ИмяСобытия = "ВыполнитьПечатьПриложенияЧекККМ" Тогда
        // Здесь мы можем безопасно запустить локальную процедуру печати родительской формы
        ВыполнитьЛокальнуюПечатьЧека(Параметр.Ссылка);
    КонецЕсли;
КонецПроцедуры

При таком подходе формы ничего не знают о внутреннем устройстве друг друга. Дочерняя форма просто «кричит» в систему о своем желании распечатать документ, а родительская форма «слушает» этот сигнал и отрабатывает его.

---

Способ 3. Прямой вызов экспортного клиентского метода формы-владельца

Если мы точно уверены, что дочерняя форма всегда вызывается из конкретной родительской формы, мы можем вызвать экспортный метод владельца напрямую. Однако здесь действует важнейшее правило платформы 1С: напрямую из внешнего кода формы можно вызвать только те процедуры и функции, которые объявлены с директивой &НаКлиенте Экспорт.

  1. В родительской форме объявляем экспортную клиентскую процедуру:

&НаКлиенте
Процедура ВыполнитьПечатьПриложенияЭкспорт() Экспорт
    // Вызываем локальный серверный метод печати формы
    ТабДок = СформироватьПечатьНаСервере();
    ТабДок.Показать();
КонецПроцедуры
  1. В дочерней форме пишем безопасный вызов этого метода через проверку типа владельца:

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

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

← На главную