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