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

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

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

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

В чем сложность передачи данных на управляемых формах?

Прежде чем перейти к коду, давайте выясним причину частых ошибок при реализации подбора. На управляемом клиенте все данные форм представлены специальными типами-обертками. Например, таблица значений на форме имеет тип ДанныеФормыКоллекция, а строка таблицы — ДанныеФормыЭлементКоллекции. Эти объекты жестко связаны с контекстом той формы, в которой они существуют.

Критическая ошибка: Попытка передать реквизит формы типа ДанныеФормыКоллекция (например, напрямую таблицу со штрихкодами) из второй формы в первую при закрытии. Как только вторая форма закрывается, её контекст уничтожается. Если первая форма попытается прочитать переданный объект, платформа незамедлительно вызовет исключение, так как источник данных больше не существует в памяти клиента.

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

---

Способ №1. Использование механизма оповещения и закрытия формы (Рекомендуемый стандартный метод)

Этот метод основан на использовании объекта ОписаниеОповещения. Мы открываем вторую форму, передавая ей информацию о том, какую процедуру первой формы нужно вызвать при её закрытии. Вторая форма при закрытии передает безопасный массив структур с отобранными данными.

Рассмотрим по шагам реализацию этого механизма.

Шаг 1. Открытие формы подбора из формы документа

В модуле первой формы (ФормаДокумента) мы подготавливаем параметры для открытия и создаем описание оповещения, указывая процедуру-колбэк, которая выполнит заполнение документа.


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

Шаг 2. Обработка параметров и подготовка данных во второй форме

Во второй форме (ФормаПодбора) мы принимаем параметры в обработчике создания формы на сервере и заполняем таблицу для пользователя.


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Читаем параметры открытия
    ОсновноеСредство = Параметры.ОсновноеСредство;
    КоличествоДляСписания = Параметры.Количество;
    
    // Здесь мы заполняем нашу таблицу ШтрихКодыОС данными из базы данных
    ЗаполнитьТаблицуШтрихкодовНаСервере(ОсновноеСредство);
    
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьТаблицуШтрихкодовНаСервере(Знач ИсточникОС)
    
    // Пример запроса к регистру сведений штрихкодов
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Штрихкоды.Штрихкод КАК Штрихкод,
        |   ЛОЖЬ КАК Снять
        |ИЗ
        |   РегистрСведений.ШтрихкодыНоменклатуры КАК Штрихкоды
        |ГДЕ
        |   Штрихкоды.Владелец = &Владелец";
        
    Запрос.УстановитьПараметр("Владелец", ИсточникОС);
    
    ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
    
    // Заполняем реквизит формы "ШтрихКодыОС" (тип ДанныеФормыКоллекция)
    ЗначениеВРеквизитФормы(ТаблицаРезультата, "ШтрихКодыОС");
    
КонецПроцедуры

Шаг 3. Перенос выбранных строк и закрытие второй формы

Когда пользователь отметил нужные строки галочкой "Снять", он нажимает кнопку "Перенести в документ". Нам необходимо обойти таблицу, отобрать отмеченные строки, упаковать их в массив простых структур и закрыть форму, передав этот массив в качестве результата.


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

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

Шаг 4. Получение данных и заполнение первой формы

В модуле первой формы срабатывает процедура-колбэк, указанная в ОписаниеОповещения. Она принимает переданный массив и выполняет заполнение табличной части документа.


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

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

---

Способ №2. Передача данных через Временное хранилище (Оптимально для больших объемов данных)

Если во второй форме выбираются тысячи строк, передавать их напрямую через клиент в виде массива структур неэффективно с точки зрения производительности (особенно при работе через веб-клиент или на медленном интернет-соединении). Происходит избыточная сериализация данных в XML/JSON.

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

Проанализируем этот элегантный алгоритм:

Шаг 1. Помещение данных в хранилище при закрытии второй формы

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


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

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

Шаг 2. Получение данных по адресу в первой форме

В процедуре-оповещении первой формы мы принимаем адрес и передаем его на сервер для окончательной обработки.


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

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

---

Способ №3. Классический выбор через механизм "Оповещение о выборе"

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

Посмотрим на особенности этого метода:

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

Важное архитектурное замечание: Не путайте и не совмещайте одновременно вызовы ОповеститьОВыборе() и использование ОписаниеОповещенияОЗакрытии. Если вы вызываете ОповеститьОВыборе() в форме, открытой с описанием оповещения о закрытии, то в процедуру-колбэк оповещения о закрытии в качестве результата придет значение Неопределено. Используйте либо четко первый способ (с Закрыть(Результат)), либо третий способ (с ОповеститьОВыборе() и событием ОбработкаВыбора).

---

Сводная таблица сравнения методов

Критерий сравнения Способ №1 (ОписаниеОповещения) Способ №2 (Временное хранилище) Способ №3 (ОповеститьОВыборе)
Объем передаваемых данных Малый и средний (до 50-100 строк) Любой, в том числе огромные таблицы данных Преимущественно единичные значения
Нагрузка на сеть (трафик) Средняя (из-за сериализации коллекций на клиенте) Минимальная (передается только строковый адрес) Средняя
Сложность реализации Средняя Выше средней (требуется серверный метод выгрузки) Низкая
Безопасность типов данных Высокая (передаются только структуры и массивы) Максимальная (передается стандартная ТЗ на сервере) Высокая

Подводя итог, можно сказать следующее: для реализации простых подборов или единичного выбора отлично подходит стандартный механизм ОписаниеОповещения с возвратом массива структур через процедуру Закрыть(). Если же перед вами стоит задача переноса объемных табличных частей со множеством колонок и строк, то вашим безальтернативным выбором с точки зрения производительности и чистоты архитектуры должно стать Временное хранилище.

← На главную