При переходе с обычных форм на управляемые в платформе «1С:Предприятие 8» разработчики часто сталкиваются с неожиданным поведением платформы. Типичный сценарий: вы создаете реквизит формы с типом ТаблицаЗначений, но при обращении к нему в коде на сервере обнаруживаете, что его тип внезапно изменился на ДанныеФормыКоллекция. Попытки преобразовать его обратно через стандартные функции приводят к потере колонок и данных.
В этой статье мы подробно разберем, почему платформа ведет себя именно так, исследуем внутренние механизмы работы с данными управляемых форм и рассмотрим практические сценарии корректной работы с коллекциями как в конфигураторе, так и динамически в коде.
Для понимания этой логики проанализируем архитектуру управляемого приложения 1С. В отличие от обычного приложения, где форма и данные были единым целым и существовали в одном адресном пространстве, в управляемом приложении реализовано строгое разделение интерфейса и бизнес-логики.
Объект ТаблицаЗначений — это мощный, но тяжеловесный прикладной объект, предназначенный для выполнения сложных операций с данными на стороне сервера СУБД. Он содержит в себе избыточную для интерфейса информацию и не оптимизирован для передачи по каналам связи между сервером и тонким или веб-клиентом.
Чтобы минимизировать сетевой трафик и ускорить отрисовку интерфейса, разработчики платформы внедрили специальные типы-транспорты. Для таблиц значений таким типом стал ДанныеФормыКоллекция. Этот объект хранит только те данные, которые непосредственно отображаются на форме или используются в ее логике, и передает на клиент только измененные пользователем строки.
Даже когда выполнение кода происходит НаСервере в модуле управляемой формы, реквизиты продолжают оставаться объектами типа ДанныеФормыКоллекция. Это сделано для того, чтобы платформе не приходилось выполнять ресурсоемкую конвертацию типов туда и обратно при каждом серверном вызове.
Рассмотрим классическую ошибку, которую совершают разработчики при попытке обойти это ограничение. Схема действий обычно выглядит так:
РеквизитФормыВЗначение("ИмяРеквизита").Таб.Колонки.Добавить("НоваяКолонка") и заполняет их данными.ЗначениеВРеквизитФормы(Таб, "ИмяРеквизита").Почему это происходит? Дело в том, что метод РеквизитФормыВЗначение инициализирует таблицу значений строго на основании структуры, которая зарегистрирована для этого реквизита в метаданных самой формы. Когда вы вызываете обратный метод ЗначениеВРеквизитФормы, платформа сверяет структуру передаваемой таблицы со структурой реквизита формы. Все колонки, которых изначально не было в описании реквизита на форме, просто игнорируются и отсекаются, так как форме физически некуда их записывать.
Таким образом, чтобы колонки не терялись, их структура должна быть заранее известна форме. Реализовать это можно двумя способами, которые мы разберем далее.
Если состав колонок вашей таблицы известен на этапе разработки, это самый простой и надежный вариант. Выполним следующие шаги:
ТаблицаЗначений.После этого при вызове метода РеквизитФормыВЗначение платформа будет генерировать таблицу значений, уже содержащую все описанные колонки. При обратной записи через ЗначениеВРеквизитФормы все данные успешно сохранятся и будут доступны на форме.
Что делать, если структура таблицы заранее неизвестна? Например, состав колонок зависит от настроек пользователя или формируется на основе динамического SQL-запроса. В этом случае нам необходимо программно изменить структуру самой управляемой формы на сервере.
Для этого используется метод формы ИзменитьРеквизиты. Рассмотрим по шагам, как правильно реализовать этот процесс в серверной процедуре:
&НаСервере
Процедура СоздатьТаблицуИКолонкиДинамически()
// Шаг 1. Подготовим массивы для добавления и удаления реквизитов
МассивДобавляемыхРеквизитов = Новый Массив;
МассивУдаляемыхРеквизитов = Новый Массив;
// Если реквизит-таблица еще не создан в конфигураторе, создадим его
// Третьим параметром передается путь (для колонок это имя таблицы-родителя)
ИмяТаблицы = "ДинамическаяТаблица";
// Добавляем саму таблицу значений на форму
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы(ИмяТаблицы, Новый ОписаниеТипов("ТаблицаЗначений"), , "Моя таблица"));
// Добавляем описание колонок, указывая путь к родителю
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы("КодТовара", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(10)), ИмяТаблицы, "Код"));
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(10, 2)), ИмяТаблицы, "Количество"));
// Шаг 2. Применяем изменения структуры реквизитов на форме
ЭтаФорма.ИзменитьРеквизиты(МассивДобавляемыхРеквизитов, МассивУдаляемыхРеквизитов);
// Теперь наш реквизит "ДинамическаяТаблица" на форме обладает нужной структурой.
// Заполним его тестовыми данными через Таблицу Значений:
ТаблицаОбъект = Новый ТаблицаЗначений;
ТаблицаОбъект.Колонки.Добавить("КодТовара", Новый ОписаниеТипов("Строка"));
ТаблицаОбъект.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
НоваяСтрока = ТаблицаОбъект.Добавить();
НоваяСтрока.КодТовара = "000000123";
НоваяСтрока.Количество = 15;
// Копируем данные из объекта ТаблицаЗначений в реквизит формы
ЗначениеВРеквизитФормы(ТаблицаОбъект, ИмяТаблицы);
КонецПроцедуры
После выполнения этого кода данные физически появятся в реквизите формы, но пользователь их не увидит. Чтобы отобразить динамическую таблицу на экране, необходимо также программно создать элементы визуального интерфейса:
&НаСервере
Процедура ОтобразитьТаблицуНаФорме()
ИмяТаблицы = "ДинамическаяТаблица";
// Создаем элемент управления - Таблицу формы
ЭлементТаблица = Элементы.Добавить(ИмяТаблицы, Тип("ТаблицаФормы"), ЭтаФорма);
ЭлементТаблица.ПутьКДанным = ИмяТаблицы;
ЭлементТаблица.ОтображениеШапки = Истинный;
// Создаем визуальные поля для каждой колонки
ПолеКод = Элементы.Добавить("КодТовара", Тип("ПолеФормы"), ЭлементТаблица);
ПолеКод.Вид = ВидПоляФормы.ПолеВвода;
ПолеКод.ПутьКДанным = ИмяТаблицы + ".КодТовара";
ПолеКол = Элементы.Добавить("Количество", Тип("ПолеФормы"), ЭлементТаблица);
ПолеКол.Вид = ВидПоляФормы.ПолеВвода;
ПолеКол.ПутьКДанным = ИмяТаблицы + ".Количество";
КонецПроцедуры
Рассмотрим несколько важных практических аспектов, которые помогут вам писать производительный код и избегать лишней нагрузки на сервер.
1. Как узнать структуру колонок без конвертации в Таблицу Значений?
Если вам нужно просто проанализировать, какие колонки присутствуют в реквизите формы, не стоит вызывать тяжелый метод РеквизитФормыВЗначение. У объекта ДанныеФормыКоллекция нет прямого свойства Колонки, но мы можем использовать метод формы ПолучитьРеквизиты. Он возвращает массив описаний реквизитов:
&НаСервере
Функция ПолучитьСписокКолонокТаблицы(ИмяРеквизитаТаблицы)
СписокКолонок = Новый Массив;
МассивРеквизитов = ЭтаФорма.ПолучитьРеквизиты(ИмяРеквизитаТаблицы);
Для Каждого Реквизит Из МассивРеквизитов Цикл
СписокКолонок.Добавить(Реквизит.Имя);
КонецЦикла;
Возврат СписокКолонок;
КонецФункции
2. Быстрая загрузка результатов запроса
Если у вас есть готовый результат запроса, выгруженный в таблицу значений, и ее структура колонок совпадает с реквизитом формы, вам не нужно обходить строки в цикле и заполнять форму вручную. Используйте прямое присвоение через ЗначениеВРеквизитФормы. Платформа выполнит эту операцию на уровне платформенного C++ кода максимально быстро:
&НаСервере
Процедура ЗаполнитьТаблицуЗапросом()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Код КАК КодТовара,
| Емкость КАК Количество
|ИЗ
| Справочник.Номенклатура";
ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
// Быстро заполняем реквизит формы "ДинамическаяТаблица" данными запроса
ЗначениеВРеквизитФормы(ТаблицаРезультата, "ДинамическаяТаблица");
КонецПроцедуры
Подведем итог. Объект ДанныеФормыКоллекция — это не досадное ограничение платформы, а важный инструмент оптимизации клиент-серверного взаимодействия. Понимая разницу между ним и ТаблицейЗначений, вы сможете проектировать быстрые, стабильные и гибкие управляемые интерфейсы любой сложности.