В разработке на платформе 1С:Предприятие 8.3 одной из наиболее частых задач является реализация формы подбора. Типичный сценарий: из основной формы документа пользователь открывает вспомогательную форму (например, форму подбора штрихкодов или номенклатуры), отмечает там галочками нужные строки, закрывает её, и эти строки должны автоматически перенестись обратно в документ. На управляемых формах этот процесс имеет ряд важных архитектурных нюансов, пренебрежение которыми приводит к трудноловимым ошибкам времени выполнения.
В этой статье мы подробно разберем, как правильно организовать передачу данных между двумя управляемыми формами, проанализируем типичные ошибки разработчиков и рассмотрим три надежных метода реализации этого механизма.
Прежде чем перейти к коду, давайте выясним причину частых ошибок при реализации подбора. На управляемом клиенте все данные форм представлены специальными типами-обертками. Например, таблица значений на форме имеет тип ДанныеФормыКоллекция, а строка таблицы — ДанныеФормыЭлементКоллекции. Эти объекты жестко связаны с контекстом той формы, в которой они существуют.
Критическая ошибка: Попытка передать реквизит формы типа ДанныеФормыКоллекция (например, напрямую таблицу со штрихкодами) из второй формы в первую при закрытии. Как только вторая форма закрывается, её контекст уничтожается. Если первая форма попытается прочитать переданный объект, платформа незамедлительно вызовет исключение, так как источник данных больше не существует в памяти клиента.
Для безопасного обмена данными мы должны использовать только универсальные сериализуемые типы (такие как Массив, Структура, Соответствие) или прибегать к передаче данных через сервер с использованием Временного хранилища.
---
Этот метод основан на использовании объекта ОписаниеОповещения. Мы открываем вторую форму, передавая ей информацию о том, какую процедуру первой формы нужно вызвать при её закрытии. Вторая форма при закрытии передает безопасный массив структур с отобранными данными.
Рассмотрим по шагам реализацию этого механизма.
Шаг 1. Открытие формы подбора из формы документа
В модуле первой формы (ФормаДокумента) мы подготавливаем параметры для открытия и создаем описание оповещения, указывая процедуру-колбэк, которая выполнит заполнение документа.
&НаКлиенте
Процедура ОткрытьФормуПодбораШтрихкодов(Команда)
ТекущиеДанные = Элементы.ОсновныеСредства.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
ПоказатьПредупреждение(, "Выберите строку с основным средством!");
Возврат;
КонецЕсли;
// Формируем параметры для передачи во вторую форму
ПараметрыОткрытия = Новый Структура;
ПараметрыОткрытия.Вставить("ОсновноеСредство", ТекущиеДанные.ОсновноеСредство);
ПараметрыОткрытия.Вставить("Количество", ТекущиеДанные.Количество);
// Дополнительные параметры, которые вернутся обратно в процедуру заполнения
ДополнительныеПараметры = Новый Структура("ИдентификаторСтроки", ТекущиеДанные.ПолучитьИдентификатор());
// Создаем описание оповещения (указываем имя процедуры, модуль и доп. параметры)
ОповещениеОЗакрытии = Новый ОписаниеОповещения("ПослеЗакрытияФормыПодбора", ЭтотОбъект, ДополнительныеПараметры);
// Открываем форму модально по отношению к текущему окну
ОткрытьФорму("Документ.МойДокумент.Форма.ФормаПодбора",
ПараметрыОткрытия,
ЭтотОбъект,
УникальныйИдентификатор,
,
,
ОповещениеОЗакрытии,
РежимОткрытияОкнаФормы.БлокироватьВесьИнтерфейс);
КонецПроцедуры
Шаг 2. Обработка параметров и подготовка данных во второй форме
Во второй форме (ФормаПодбора) мы принимаем параметры в обработчике создания формы на сервере и заполняем таблицу для пользователя.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Читаем параметры открытия
ОсновноеСредство = Параметры.ОсновноеСредство;
КоличествоДляСписания = Параметры.Количество;
// Здесь мы заполняем нашу таблицу ШтрихКодыОС данными из базы данных
ЗаполнитьТаблицуШтрихкодовНаСервере(ОсновноеСредство);
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьТаблицуШтрихкодовНаСервере(Знач ИсточникОС)
// Пример запроса к регистру сведений штрихкодов
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Штрихкоды.Штрихкод КАК Штрихкод,
| ЛОЖЬ КАК Снять
|ИЗ
| РегистрСведений.ШтрихкодыНоменклатуры КАК Штрихкоды
|ГДЕ
| Штрихкоды.Владелец = &Владелец";
Запрос.УстановитьПараметр("Владелец", ИсточникОС);
ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
// Заполняем реквизит формы "ШтрихКодыОС" (тип ДанныеФормыКоллекция)
ЗначениеВРеквизитФормы(ТаблицаРезультата, "ШтрихКодыОС");
КонецПроцедуры
Шаг 3. Перенос выбранных строк и закрытие второй формы
Когда пользователь отметил нужные строки галочкой "Снять", он нажимает кнопку "Перенести в документ". Нам необходимо обойти таблицу, отобрать отмеченные строки, упаковать их в массив простых структур и закрыть форму, передав этот массив в качестве результата.
&НаКлиенте
Процедура ПеренестиИЗакрыть(Команда)
// Получаем массив отобранных данных
МассивОтобранныхДанных = ПолучитьМассивВыбранныхШтрихкодов();
Если МассивОтобранныхДанных.Количество() = 0 Тогда
ПоказатьПредупреждение(, "Не выбрано ни одного штрихкода!");
Возврат;
КонецЕсли;
// Закрываем форму и передаем массив в процедуру оповещения первой формы
Закрыть(МассивОтобранныхДанных);
КонецПроцедуры
&НаКлиенте
Функция ПолучитьМассивВыбранныхШтрихкодов()
Результат = Новый Массив;
// Обходим коллекцию данных формы на клиенте
Для Каждого СтрокаТЧ Из ШтрихКодыОС Цикл
Если СтрокаТЧ.Снять Тогда
// Создаем простую структуру, безопасную для передачи
СтруктураСтроки = Новый Структура;
СтруктураСтроки.Вставить("Штрихкод", СтрокаТЧ.Штрихкод);
Результат.Добавить(СтруктураСтроки);
EndЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Шаг 4. Получение данных и заполнение первой формы
В модуле первой формы срабатывает процедура-колбэк, указанная в ОписаниеОповещения. Она принимает переданный массив и выполняет заполнение табличной части документа.
&НаКлиенте
Процедура ПослеЗакрытияФормыПодбора(РезультатЗакрытия, ДополнительныеПараметры) Экспорт
// Если пользователь просто закрыл форму крестиком или нажал "Отмена"
Если РезультатЗакрытия = Неопределено Тогда
Возврат;
КонецЕсли;
// РезультатЗакрытия — это наш массив структур со штрихкодами
// ДополнительныеПараметры содержит ИдентификаторСтроки документа, которую мы редактируем
ОбработатьВыбранныеШтрихкодыНаСервере(РезультатЗакрытия, ДополнительныеПараметры.ИдентификаторСтроки);
КонецПроцедуры
&НаСервере
Процедура ОбработатьВыбранныеШтрихкодыНаСервере(Знач МассивШтрихкодов, Знач ИдентификаторСтроки)
// Получаем объект редактируемой строки табличной части документа
СтрокаДокумента = Объект.ОсновныеСредства.НайтиПоИдентификатору(ИдентификаторСтроки);
Если СтрокаДокумента <> Неопределено Тогда
// Очищаем или заполняем штрихкоды в зависимости от вашей бизнес-логики
// Для примера запишем первый выбранный штрихкод в текущую строку
Если МассивШтрихкодов.Количество() > 0 Тогда
СтрокаДокумента.Штрихкод = МассивШтрихкодов[0].Штрихкод;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
---
Если во второй форме выбираются тысячи строк, передавать их напрямую через клиент в виде массива структур неэффективно с точки зрения производительности (особенно при работе через веб-клиент или на медленном интернет-соединении). Происходит избыточная сериализация данных в XML/JSON.
В этом случае отличным решением является использование Временного хранилища. Данные выгружаются в полноценную ТаблицуЗначений на сервере второй формы, помещаются в хранилище, а на клиент передается лишь короткая строка — адрес во временном хранилище. Первая форма на сервере получает данные из этого адреса и обрабатывает их.
Проанализируем этот элегантный алгоритм:
Шаг 1. Помещение данных в хранилище при закрытии второй формы
Во второй форме при подтверждении выбора мы вызываем серверный метод, формируем классическую таблицу значений и помещаем её во временное хранилище.
&НаКлиенте
Процедура ПеренестиИЗакрытьВременноеХранилище(Команда)
// Помещаем таблицу в хранилище на сервере и получаем уникальный адрес
АдресДанных = ПоместитьДанныеВХранилищеНаСервере();
// Закрываем форму и передаем только адрес (строку)
Закрыть(АдресДанных);
КонецПроцедуры
&НаСервере
Функция ПоместитьДанныеВХранилищеНаСервере()
// Конвертируем коллекцию управляемой формы в обычную Таблицу Значений
ТаблицаДляВыгрузки = РеквизитФормыВЗначение("ШтрихКодыОС", Тип("ТаблицаЗначений"));
// Отбираем только нужные строки, где Снять = Истина
МассивСтрокДляУдаления = Новый Массив;
Для Каждого СтрокаТЗ Из ТаблицаДляВыгрузки Цикл
Если Не СтрокаТЗ.Снять Тогда
МассивСтрокДляУдаления.Добавить(СтрокаТЗ);
КонецЕсли;
КонецЦикла;
Для Каждого СтрокаДляУдаления Из МассивСтрокДляУдаления Цикл
ТаблицаДляВыгрузки.Удалить(СтрокаДляУдаления);
КонецЦикла;
// Помещаем очищенную таблицу во временное хранилище.
// Передавая УникальныйИдентификатор текущей формы, мы гарантируем,
// что платформа автоматически очистит память сервера после закрытия этого окна.
Возврат ПоместитьВоВременноеХранилище(ТаблицаДляВыгрузки, УникальныйИдентификатор);
КонецФункции
Шаг 2. Получение данных по адресу в первой форме
В процедуре-оповещении первой формы мы принимаем адрес и передаем его на сервер для окончательной обработки.
&НаКлиенте
Процедура ПослеЗакрытияФормыВХ(РезультатЗакрытия, ДополнительныеПараметры) Экспорт
Если ПустаяСтрока(РезультатЗакрытия) Тогда
Возврат;
КонецЕсли;
// Передаем адрес временного хранилища на сервер
ЗаполнитьДокументИзХранилищаНаСервере(РезультатЗакрытия);
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьДокументИзХранилищаНаСервере(Знач АдресВХ)
// Извлекаем полноценную Таблицу Значений из временного хранилища
ТаблицаШтрихкодов = ПолучитьИзВременногоХранилища(АдресВХ);
Если ТипЗнч(ТаблицаШтрихкодов) = Тип("ТаблицаЗначений") Тогда
// Быстро обрабатываем таблицу значений на сервере в одной транзакции
Для Каждого СтрокаВыбора Из ТаблицаШтрихкодов Цикл
НоваяСтрока = Объект.ШтрихкодыДокумента.Добавить();
НоваяСтрока.Штрихкод = СтрокаВыбора.Штрихкод;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
---
Этот способ традиционен для платформы 1С и подходит для простых сценариев выбора единичных значений или простых списков, когда форма подбора открывается с явным указанием владельца формы.
Посмотрим на особенности этого метода:
ПараметрыФормы = Новый Структура;
ФормаВыбора = ПолучитьФорму("Документ.МойДокумент.Форма.ФормаПодбора", ПараметрыФормы, Элементы.ОсновныеСредства);
ФормаВыбора.Открыть();
ОповеститьОВыборе(МассивВыбранныхЗначений);
Этот метод автоматически закрывает текущую форму и передает данные владельцу.
ОбработкаВыбора:
&НаКлиенте
Процедура ОсновныеСредстваОбработкаВыбора(Элемент, ВыбранноеЗначение, СтандартнаяОбработка)
// Предотвращаем стандартное поведение платформы
СтандартнаяОбработка = Ложь;
// Обрабатываем массив выбранных значений
Если ТипЗнч(ВыбранноеЗначение) = Тип("Массив") Тогда
// Наш код заполнения...
КонецЕсли;
КонецПроцедуры
Важное архитектурное замечание: Не путайте и не совмещайте одновременно вызовы ОповеститьОВыборе() и использование ОписаниеОповещенияОЗакрытии. Если вы вызываете ОповеститьОВыборе() в форме, открытой с описанием оповещения о закрытии, то в процедуру-колбэк оповещения о закрытии в качестве результата придет значение Неопределено. Используйте либо четко первый способ (с Закрыть(Результат)), либо третий способ (с ОповеститьОВыборе() и событием ОбработкаВыбора).
---
| Критерий сравнения | Способ №1 (ОписаниеОповещения) | Способ №2 (Временное хранилище) | Способ №3 (ОповеститьОВыборе) |
|---|---|---|---|
| Объем передаваемых данных | Малый и средний (до 50-100 строк) | Любой, в том числе огромные таблицы данных | Преимущественно единичные значения |
| Нагрузка на сеть (трафик) | Средняя (из-за сериализации коллекций на клиенте) | Минимальная (передается только строковый адрес) | Средняя |
| Сложность реализации | Средняя | Выше средней (требуется серверный метод выгрузки) | Низкая |
| Безопасность типов данных | Высокая (передаются только структуры и массивы) | Максимальная (передается стандартная ТЗ на сервере) | Высокая |
Подводя итог, можно сказать следующее: для реализации простых подборов или единичного выбора отлично подходит стандартный механизм ОписаниеОповещения с возвратом массива структур через процедуру Закрыть(). Если же перед вами стоит задача переноса объемных табличных частей со множеством колонок и строк, то вашим безальтернативным выбором с точки зрения производительности и чистоты архитектуры должно стать Временное хранилище.