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