Открытие второй формы внешнего отчета или обработки с передачей и сохранением табличной части

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

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

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

Почему возникает ошибка определения типа при работе с внешними объектами?

Рассмотрим сначала распространенную ошибку, с которой сталкиваются разработчики при попытке передать объект во вторую форму. При использовании конструкции вида ДанныеФормыВЗначение(Параметры.ТекущийОбъект, Тип("ВнешнийОтчет.ИнвентаризационнаяОписьТМЦ")) платформа 1С возвращает ошибку «Тип не определен» или «Несоответствие типов».

Выясним причину этого поведения. Она кроется в двух аспектах работы платформы:

  1. Неверное пространство имен для типов объектов. Для внешних отчетов и обработок типом объекта является ВнешнийОтчетОбъект.[ИмяОтчета] или ВнешняяОбработкаОбъект.[ИмяОбработки]. Использование конструкции ВнешнийОтчет.[Имя] ошибочно, так как данный тип описывает менеджер объекта, а не сам объект в оперативной памяти.
  2. Динамические имена типов. Когда внешний отчет или обработка запускается из файла или регистрируется через механизм БСП (Библиотека Стандартных Подсистем) в справочнике «Дополнительные отчеты и обработки», платформа регистрирует этот объект под динамическим именем (например, с уникальным идентификатором в имени типа). Из-за этого написать статическое имя типа в коде конфигурации или внешней обработки становится невозможно.

Чтобы обойти это ограничение и безопасно получить объект на сервере во второй форме, мы можем использовать метод РеквизитФормыВЗначение(). Ему не требуется передавать тип вторым параметром — платформа сама автоматически определит и восстановит объект:


// В контексте модуля вспомогательной формы
ОтчетОбъект = РеквизитФормыВЗначение("Отчет"); 

Если вам по какой-то причине обязательно требуется использовать именно функцию ДанныеФормыВЗначение(), то тип можно получить динамическим путем, как показано в примере ниже:


ТипОбъекта = ТипЗнч(РеквизитФормыВЗначение("Отчет"));
ОтчетОбъект = ДанныеФормыВЗначение(Параметры.ТекущийОбъект, ТипОбъекта);

Однако передача всего контекста («тяжелого» объекта) между формами — не самый оптимальный путь с точки зрения производительности клиент-серверного взаимодействия. Ниже мы детально разберем лучшие практики передачи и возврата табличной части.

Способ 1. Передача данных через Временное хранилище (Рекомендуемый стандарт 1С)

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

Разберем по шагам реализацию данного механизма.

Шаг 1. Код в основной форме отчета

В модуле основной формы при нажатии на кнопку открытия настроек мы помещаем данные табличной части во временное хранилище и передаем полученный адрес во вторую форму через параметры. Также мы настраиваем описание оповещения для обработки возвращаемых данных:


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

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

&НаКлиенте
Процедура ПослеЗакрытияФормыНастроек(РезультатЗакрытия, ДополнительныеПараметры)
    // РезультатЗакрытия вернет новый адрес во временном хранилище с измененными данными
    Если ЗначениеЗаполнено(РезультатЗакрытия) Тогда
        ЗагрузитьТЧИзХранилища(РезультатЗакрытия);
    КонецЕсли;
КонецПроцедуры

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

Шаг 2. Код во второй форме (ФормаНастроек)

Во вспомогательной форме мы считываем данные из временного хранилища при ее создании, а перед закрытием — помещаем измененные данные обратно и передаем адрес вызывающей форме:


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

&НаКлиенте
Процедура СохранитьИЗакрыть(Команда)
    // Помещаем измененную табличную часть в хранилище и возвращаем адрес в форму-владелец
    АдресИзменений = СохранитьТЧВХранилище();
    Закрыть(АдресИзменений);
КонецПроцедуры

&НаСервере
Функция СохранитьТЧВХранилище()
    Возврат ПоместитьВоВременноеХранилище(Отчет.СписокМПЗ.Выгрузить(), УникальныйИдентификатор);
КонецФункции

Способ 2. Прямое копирование данных формы (через Владельца формы)

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

Проанализируем этот подход подробнее. Важно правильно определить источник и приемник данных в зависимости от типа вашего объекта (для отчетов основным реквизитом обычно является Отчет, а для обработок — Объект).

Реализация в основной форме:


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

Реализация во вспомогательной форме (при сохранении и закрытии):


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

Важное примечание к Способу 2: Главный недостаток прямого копирования — жесткая связанность (high coupling) ваших форм. Вторая форма должна "знать" о структуре первой формы и о наличии у нее конкретного реквизита Отчет. Если вы решите использовать эту форму настроек для другого отчета, вам придется переписывать код. Метод с Временным хранилищем лишен этой проблемы.

Способ 3. Передача легкого массива структур (для небольших табличных частей)

Если ваша табличная часть гарантированно содержит небольшое количество строк (например, до 50–100 строк настроек), пересылку данных можно организовать в виде массива структур без задействования механизмов временного хранилища на сервере.

Посмотрим на пример реализации данного подхода.

Код во второй форме при закрытии:


&НаКлиенте
Процедура ЗакрытьСПередачейМассива(Команда)
    // Формируем массив структур на сервере
    ДанныеДляВозврата = ПолучитьМассивСтруктурТЧ();
    Закрыть(ДанныеДляВозврата);
КонецПроцедуры

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

Код обработчика оповещения в первой форме:


&НаКлиенте
Процедура ПослеЗакрытияФормыСМассивом(РезультатЗакрытия, ДополнительныеПараметры)
    Если ТипЗнч(РезультатЗакрытия) = Тип("Массив") Тогда
        ЗаполнитьТЧИзМассиваНаСервере(РезультатЗакрытия);
    КонецЕсли;
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьТЧИзМассиваНаСервере(МассивДанных)
    Отчет.СписокМПЗ.Очистить();
    Для Каждого СтрокаСтруктуры Из МассивДанных Цикл
        НоваяСтрока = Отчет.СписокМПЗ.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаСтруктуры);
    КонецЦикла;
КонецПроцедуры

Сравнительный анализ методов

Для того чтобы выбрать оптимальный инструмент для вашей задачи, сопоставим рассмотренные подходы:

Таким образом, для большинства практических задач рекомендуется использовать Способ 1 (Временное хранилище) как наиболее надежное и масштабируемое решение в архитектуре управляемого приложения 1С.

← На главную