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