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