При разработке в управляемом приложении 1С программисты часто сталкиваются со спецификой работы платформы с незаписанными объектами. Типичная задача: нужно программно создать несколько новых документов в цикле, заполнить их данными и открыть их формы пользователю для дальнейшего редактирования и ручной записи. Однако при стандартном вызове метода открытия формы открывается лишь одно окно, данные в котором постоянно перезаписываются данными из последней итерации цикла. Выясним причину такого поведения платформы и разберем по шагам, как решить эту проблему правильно и эффективно.
Для того чтобы понять, почему платформа ведет себя именно так, проанализируем внутренние механизмы работы управляемых форм. Когда мы открываем форму существующего (уже записанного в базу данных) документа, платформа автоматически использует его уникальную ссылку Ссылка в качестве ключа уникальности. Это гарантирует, что для одного и того же элемента базы данных не откроется два разных окна.
Но у нового, еще не записанного документа ссылки в базе данных нет. По умолчанию система использует предопределенный для этого типа объектов ключ уникальности. Поэтому, когда мы в цикле вызываем процедуру открытия новой формы, система считает, что мы пытаемся повторно открыть одну и ту же форму нового документа. Вместо создания нового окна платформа просто находит уже открытую форму, активизирует её и заменяет в ней данные на новые.
Решением этой проблемы является правильное использование параметра Уникальность в методах ОткрытьФорму или ПолучитьФорму.
Рассмотрим параметры метода ОткрытьФорму, а именно четвертый параметр:
ОткрытьФорму(ИмяФормы, Параметры, Владелец, Уникальность, Окно, НавигационнаяСсылка, ОписаниеОповещенияОЗакрытии, РежимОткрытияОкна)
В качестве параметра Уникальность мы можем передать:
Новый УникальныйИдентификатор(). Платформа будет искать форму именно по этому ключу. Это чрезвычайно полезно, если в дальнейшем нам понадобится программно обращаться к конкретному окну из других клиентских процедур.Если наша задача заключается лишь в том, чтобы просто открыть несколько пустых форм новых документов, нам достаточно передать Истина в качестве параметра уникальности. Посмотрим на простой пример кода:
&НаКлиенте
Процедура ОткрытьНесколькоНовыхДокументов()
Для Счетчик = 1 По 5 Цикл
// Передаем Истина в качестве 4-го параметра (Уникальность)
ОткрытьФорму("Документ.РеализацияТоваровУслуг.ФормаОбъекта", , , Истина);
КонецЦикла;
КонецПроцедуры
При выполнении этого кода на экране пользователя откроется ровно пять отдельных вкладок с новыми незаписанными документами.
В реальной практике просто открыть пустые формы требуется редко. Обычно нам необходимо передать в каждый документ свои уникальные данные (контрагента, склад, список товаров и т.д.).
Поскольку на клиенте в тонком клиенте мы не можем напрямую оперировать мутабельным типом ДокументОбъект, наиболее красивым и архитектурно верным решением является передача параметров заполнения в саму форму. При создании формы система автоматически вызовет стандартный обработчик ОбработкаЗаполнения в модуле объекта нового документа.
Разберем этот вариант по шагам.
Шаг 1. Написание кода вызова на клиенте:
&НаКлиенте
Процедура СоздатьИЗаполнитьДокументы()
// Создадим массив с демонстрационными данными для заполнения
МассивДанных = Новый Массив;
Данные1 = Новый Структура("Контрагент, Комментарий", ПредопределенноеЗначение("Справочник.Контрагенты.ПустаяСсылка"), "Первый документ");
Данные2 = Новый Структура("Контрагент, Комментарий", ПредопределенноеЗначение("Справочник.Контрагенты.ПустаяСсылка"), "Второй документ");
МассивДанных.Добавить(Данные1);
МассивДанных.Добавить(Данные2);
// В цикле открываем формы с уникальными параметрами
Для Каждого СтруктураДанных Из МассивДанных Цикл
// Помещаем наши данные в специальный параметр "ЗначенияЗаполнения"
ПараметрыФормы = Новый Структура("ЗначенияЗаполнения", СтруктураДанных);
// Открываем форму с уникальным ключом Истина
ОткрытьФорму("Документ.РеализацияТоваровУслуг.ФормаОбъекта", ПараметрыФормы, , Истина);
КонецЦикла;
КонецПроцедуры
Шаг 2. Настройка обработки заполнения в модуле объекта:
В модуле объекта создаваемого документа (например, Документ.РеализацияТоваровУслуг) необходимо описать логику разбора переданных параметров:
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, ТекстЗаполнения, СтандартнаяОбработка)
Если ТипЗнч(ДанныеЗаполнения) = Тип("Структура") Тогда
// Заполняем реквизиты объекта из переданной структуры
Если ДанныеЗаполнения.Свойство("Контрагент") Тогда
ЭтотОбъект.Контрагент = ДанныеЗаполнения.Контрагент;
КонецЕсли;
Если ДанныеЗаполнения.Свойство("Комментарий") Тогда
ЭтотОбъект.Комментарий = ДанныеЗаполнения.Комментарий;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Этот способ является наиболее производительным, так как платформа берет на себя всю работу по сериализации данных и передаче их между слоями системы.
Рассмотрим ситуацию, когда конфигурация находится на поддержке без возможности изменения (или вы не хотите вносить изменения в типовые модули объектов). В этом случае мы можем получить форму программно, извлечь ее структуру данных, заполнить ее на сервере и вернуть обратно в форму.
Проанализируем пример кода, реализующий этот подход:
&НаКлиенте
Процедура ЗаполнитьФормыНапрямую()
// Предположим, у нас есть массив структур с данными для заполнения табличной части и шапки
МассивДокументовДляСоздания = ПолучитьДанныеДляСоздания();
Для Каждого СтрокаДанных Из МассивДокументовДляСоздания Цикл
// 1. Получаем новую форму с уникальным ключом Истина. Форма еще не открыта!
ФормаДокумента = ПолучитьФорму("Документ.РеализацияТоваровУслуг.ФормаОбъекта", , , Истина);
// 2. Получаем данные формы (тип ДанныеФормыСтруктура)
ДанныеФормы = ФормаДокумента.Объект;
// 3. Вызываем серверную процедуру для заполнения данных формы
ЗаполнитьДанныеФормыНаСервере(ДанныеФормы, СтрокаДанных);
// 4. Копируем обратно заполненный объект в контекст нашей формы
КопироватьДанныеФормы(ДанныеФормы, ФормаДокумента.Объект);
// 5. Теперь, когда все данные подготовлены, открываем форму пользователю
ФормаДокумента.Открыть();
КонецЦикла;
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ЗаполнитьДанныеФормыНаСервере(ДанныеФормы, ПараметрыЗаполнения)
// Преобразуем данные управляемой формы в прикладной объект ДокументОбъект
ДокОбъект = ДанныеФормыВЗначение(ДанныеФормы, Тип("ДокументОбъект.РеализацияТоваровУслуг"));
// Производим любое заполнение реквизитов и табличных частей стандартным образом
ДокОбъект.Дата = ТекущаяДата();
ДокОбъект.Контрагент = ПараметрыЗаполнения.Контрагент;
ДокОбъект.Организация = ПараметрыЗаполнения.Организация;
// Пример заполнения табличной части
НоваяСтрока = ДокОбъект.Товары.Добавить();
НоваяСтрока.Номенклатура = ПараметрыЗаполнения.Номенклатура;
НоваяСтрока.Количество = 1;
// Выполняем стандартные расчеты (например, цен или сумм), если они определены в объекте
// ДокОбъект.ЗаполнитьЦены();
// Преобразуем заполненный объект обратно в структуру данных формы
ЗначениеВДанныеФормы(ДокОбъект, ДанныеФормы);
КонецПроцедуры
Этот метод дает разработчику полную свободу действий. Мы можем использовать любые экспортные методы модуля объекта для расчета скидок, налогов и автоматического заполнения связанных реквизитов, не затрагивая при этом логику самого открытия форм.
Если документов много и передаваемые структуры содержат тяжелые данные (например, огромные таблицы товаров со множеством колонок и свойств), передавать их напрямую через параметры формы не рекомендуется — это может существенно замедлить работу клиентского приложения. В таких ситуациях отличным решением является использование Временного хранилища.
Рассмотрим схему работы этого паттерна:
ПоместитьВоВременноеХранилище(). Мы получаем уникальный адрес (строку вида e1cib/tempstorage/...).ПриСозданииНаСервере мы проверяем наличие этого параметра. Если он передан, мы извлекаем структуру из хранилища по адресу, заполняем реквизиты формы и удаляем данные из хранилища для освобождения оперативной памяти сервера.Посмотрим на пример реализации.
Код вызова на клиенте:
&НаКлиенте
Процедура СоздатьЧерезХранилище()
// Получаем список адресов временного хранилища с подготовленными данными
АдресаХранилищ = ПодготовитьДанныеНаСервере();
Для Каждого Адрес Из АдресаХранилищ Цикл
ПараметрыФормы = Новый Структура("АдресДанныхЗаполнения", Адрес);
// Открываем форму с уникальным ключом
ОткрытьФорму("Документ.РеализацияТоваровУслуг.ФормаОбъекта", ПараметрыФормы, , Истина);
КонецЦикла;
КонецПроцедуры
Код заполнения в модуле формы документа в событии ПриСозданииНаСервере:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Проверяем, передан ли наш специальный параметр
Если Параметры.Свойство("АдресДанныхЗаполнения") И ЗначениеЗаполнено(Параметры.АдресДанныхЗаполнения) Тогда
// Извлекаем данные из временного хранилища
ДанныеДляЗаполнения = ПолучитьИзВременногоХранилища(Параметры.АдресДанныхЗаполнения);
Если ДанныеДляЗаполнения <> Неопределено Тогда
// Заполняем реквизиты формы напрямую через Объект
ЗаполнитьЗначенияСвойств(Объект, ДанныеДляЗаполнения);
// Если в структуре были таблицы, заполняем и их
Если ДанныеДляЗаполнения.Свойство("Товары") Тогда
Объект.Товары.Загрузить(ДанныеДляЗаполнения.Товары);
КонецЕсли;
// Обязательно удаляем данные из хранилища, чтобы не засорять память
УдалитьИзВременногоХранилища(Параметры.АдресДанныхЗаполнения);
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Несмотря на то, что технически задача открытия множества незаписанных окон решается достаточно просто, с точки зрения архитектурного дизайна и удобства пользователей (UX/UI) этот подход имеет существенные недостатки. Опытные разработчики и руководители проектов часто стараются переубедить заказчиков в целесообразности такой реализации по следующим причинам:
Вместо открытия десятков незаписанных окон гораздо правильнее реализовать промежуточное рабочее место для пользователя (специальную обработку):
Этот подход гарантирует высокую производительность системы, исключает появление пустых номеров и делает работу пользователя контролируемой и комфортной.