Таблица значений в управляемой форме: как доставить данные с сервера до клиента

· 10 мин

Содержание

Таблицу значений нельзя напрямую вернуть с сервера на тонкий клиент 1С 8.3: на клиенте такого типа нет. Чтобы передать данные, их упаковывают. Есть три рабочих способа: положить таблицу в реквизит формы через ЗначениеВРеквизитФормы(), превратить её в массив структур или оставить на сервере во временном хранилище и передавать на клиент только адрес.

Типичная ситуация: разработчик пишет обработку, выполняет запрос в процедуре &НаСервере, делает Выгрузить() и пытается вернуть результат в клиентскую процедуру. В Конфигураторе код компилируется, а при нажатии кнопки 1С падает с ошибкой. Ниже разобраны причина, способы обойти ограничение и цена каждого способа по скорости.

Почему клиент и сервер 1С обмениваются данными не напрямую

В управляемом приложении код формы делится по директивам компиляции. Процедура с &НаКлиенте выполняется на компьютере пользователя, процедура с &НаСервере — на сервере 1С. Между ними — вызов сервера, и всё, что через него проходит, сериализуется: превращается в поток данных и собирается заново на другой стороне.

Собрать значение на клиенте можно только если его тип там существует. У типа ТаблицаЗначений в синтакс-помощнике указана доступность: сервер, толстый клиент, внешнее соединение. Тонкого и веб-клиента в этом списке нет. Поэтому:

  • 🚫 вернуть таблицу значений из серверной функции в клиентский код нельзя;
  • 🚫 создать её на клиенте через Новый ТаблицаЗначений нельзя — будет ошибка Тип не определен (ТаблицаЗначений);
  • ✅ можно передать данные таблицы в другом «контейнере», который клиент понимает.

В толстом клиенте тип существует, и прямой возврат там может сработать. Не стройте на этом логику: код управляемой формы обязан работать в тонком и веб-клиенте, иначе он сломается у первого пользователя, который зайдёт через браузер.

Есть и второй нюанс. Контекстный вызов &НаСервере из формы гонит на сервер и обратно все данные формы: реквизиты, табличные части, состояние. Вызов &НаСервереБезКонтекста передаёт только параметры и возвращаемое значение. От выбора директивы зависит, сколько килобайт уйдёт по сети при каждом нажатии кнопки.

Способы передать таблицу значений на клиент

СпособКогда подходитЧто получает клиентМинусы
Реквизит формы типа ТаблицаЗначенийДанные нужно показать пользователю в таблице формыДанныеФормыКоллекцияКолонки задаются заранее; передаются с каждым контекстным вызовом
Массив структурКлиентскому коду нужно перебрать строки и что-то посчитатьМассив из СтруктураРучная упаковка, лишние ключи в каждой строке
Временное хранилищеТаблица большая и нужна только серверу в следующих вызовахСтрока-адресНа клиенте таблицу не прочитать
СписокЗначенийНужна одна колонка значенийСписокЗначенийТолько одна колонка (плюс представление и пометка)
Строка XML или внутренний форматТаблицу надо провести через клиент и вернуть на сервер без измененийСтрокаНа клиенте не разобрать обратно в таблицу

Способ 1. Реквизит формы и ЗначениеВРеквизитФормы

Самый частый вариант. В редакторе формы на вкладке «Реквизиты» добавьте реквизит, например Товары, с типом ТаблицаЗначений, и опишите его колонки с типами. Перетащите реквизит на форму — получится элемент «Таблица».

На сервере заполните обычную таблицу значений и перенесите её в реквизит:

&НаКлиенте
Процедура Заполнить(Команда)
	ЗаполнитьНаСервере();
	// На клиенте реквизит Товары — это ДанныеФормыКоллекция
	Для Каждого СтрокаТовара Из Товары Цикл
		Сообщить(СтрШаблон("%1, артикул %2", СтрокаТовара.Номенклатура, СтрокаТовара.Артикул));
	КонецЦикла;
КонецПроцедуры

&НаСервере
Процедура ЗаполнитьНаСервере()
	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ ПЕРВЫЕ 100
	|	Номенклатура.Ссылка КАК Номенклатура,
	|	Номенклатура.Артикул КАК Артикул
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	НЕ Номенклатура.ЭтоГруппа";
	ТЗ = Запрос.Выполнить().Выгрузить();
	ЗначениеВРеквизитФормы(ТЗ, "Товары");
КонецПроцедуры

На клиенте с ДанныеФормыКоллекция работают почти как с таблицей: перебирают в цикле, обращаются к строке по индексу, вызывают Добавить(), Удалить(), Количество(), НайтиСтроки(). Методы Выгрузить() и Загрузить() у коллекции есть только на сервере.

Обратная дорога — РеквизитФормыВЗначение("Товары") в серверной процедуре. Она вернёт полноценную таблицу значений, с которой можно делать всё: группировать, сворачивать, передавать в запрос.

ЗначениеВРеквизитФормы() и РеквизитФормыВЗначение() — методы формы. В процедуре &НаСервереБезКонтекста формы нет, и вызвать их там нельзя. Для коллекций, которые не являются реквизитом формы целиком, есть глобальные ЗначениеВДанныеФормы() и ДанныеФормыВЗначение().

Способ 2. Массив структур

Когда таблицу не надо показывать, а клиентскому коду нужны значения, удобнее вернуть массив структур. Массив и Структура есть на клиенте, и если внутри лежат примитивы и ссылки, передача пройдёт без ошибок.

&НаСервереБезКонтекста
Функция ТаблицаВМассив(ТЗ)
	ИменаКолонок = "";
	Для Каждого Колонка Из ТЗ.Колонки Цикл
		ИменаКолонок = ИменаКолонок + ?(ИменаКолонок = "", "", ",") + Колонка.Имя;
	КонецЦикла;

	Результат = Новый Массив;
	Для Каждого СтрокаТЗ Из ТЗ Цикл
		Элемент = Новый Структура(ИменаКолонок);
		ЗаполнитьЗначенияСвойств(Элемент, СтрокаТЗ);
		Результат.Добавить(Элемент);
	КонецЦикла;
	Возврат Результат;
КонецФункции

&НаСервереБезКонтекста
Функция ПолучитьНоменклатуру(Знач Родитель)
	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	Номенклатура.Ссылка КАК Ссылка,
	|	Номенклатура.Наименование КАК Наименование
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	Номенклатура.Родитель = &Родитель";
	Запрос.УстановитьПараметр("Родитель", Родитель);
	Возврат ТаблицаВМассив(Запрос.Выполнить().Выгрузить());
КонецФункции

На клиенте результат перебирают так: Для Каждого Элемент Из Данные Цикл ... Элемент.Наименование .... Функцию упаковки лучше вынести в серверный общий модуль, чтобы не копировать её по формам. В типовых конфигурациях на БСП похожая функция уже есть в общем модуле ОбщегоНазначения — поищите по слову «ТаблицаЗначенийВМассив».

Если колонка одна, не нужен ни массив структур, ни реквизит. Верните ТЗ.ВыгрузитьКолонку("Ссылка") — это обычный массив значений.

Способ 3. Временное хранилище

Бывает, что таблица в 50 тысяч строк нужна только серверу: на первом шаге её собирают, на втором — проверяют, на третьем — записывают документы. Гонять её через клиент незачем. Положите таблицу во временное хранилище и держите на клиенте только адрес:

&НаСервере
Процедура ПодготовитьДанные()
	ТЗ = СобратьДанные(); // любая серверная функция, возвращающая таблицу значений
	АдресДанных = ПоместитьВоВременноеХранилище(ТЗ, УникальныйИдентификатор);
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПроверитьДанные(Знач Адрес)
	ТЗ = ПолучитьИзВременногоХранилища(Адрес);
	Возврат ТЗ.Количество();
КонецФункции

АдресДанных — реквизит формы типа «Строка». Второй параметр УникальныйИдентификатор привязывает значение к форме: оно живёт, пока форма открыта. Если идентификатор формы не передать, платформа может удалить значение уже после следующего вызова сервера, и на втором шаге вы получите Неопределено вместо таблицы.

Если таблица больше не нужна, а форма ещё открыта, освободите память явно: УдалитьИзВременногоХранилища(Адрес).

Способ 4. Сериализация в строку

Таблицу можно превратить в строку на сервере: через ЗначениеВСтрокуВнутр() или через СериализаторXDTO в XML. Строку клиент примет. Разобрать её обратно в таблицу можно только там, где тип существует, то есть снова на сервере.

Этот вариант нужен редко: например, когда таблицу надо сохранить в файл на компьютере пользователя или передать в другую базу. Для обычной работы формы временное хранилище проще и быстрее.

Какие типы можно передать на клиент, а какие нет

Ограничение касается не только самой таблицы, но и содержимого её ячеек. Структура со значением недопустимого типа тоже не пройдёт.

Передаётся на клиентНе передаётся
Строка, Число, Дата, Булево, Неопределено, NullТаблицаЗначений, ДеревоЗначений
Ссылки на объекты (СправочникСсылка, ДокументСсылка и т.д.)Объекты (СправочникОбъект, ДокументОбъект)
Массив, Структура, Соответствие и их фиксированные вариантыЗапрос, РезультатЗапроса, выборки
СписокЗначенийМенеджерВременныхТаблиц
УникальныйИдентификатор, ДвоичныеДанныеТабличные части объекта, ОбъектМетаданных
Тип, ОписаниеТиповCOMОбъект

Правило проверки простое: откройте тип в синтакс-помощнике и посмотрите строку «Доступность». Если там нет тонкого клиента , значение такого типа на клиент не попадёт.

Отдельная ловушка — колонки без типа. Если в таблице, полученной кодом, колонка добавлена без описания типов, в ней может оказаться что угодно, в том числе объект. Ошибка всплывёт не на всех данных, а на той строке, где лежит неподходящее значение.

Как ускорить передачу таблицы значений между сервером и клиентом

Каждый серверный вызов — это задержка сети плюс объём данных. На локальной сети разница незаметна, а у пользователя на удалённом подключении или в веб-клиенте лишние сотни килобайт превращают кнопку в «зависание».

Передавайте только нужные колонки

Запрос часто возвращает больше полей, чем нужно форме. Перед упаковкой оставьте нужные:

ТЗДляКлиента = ТЗ.Скопировать(, "Номенклатура,Количество");

Первый параметр метода Скопировать() — отбор строк, второй — список колонок через запятую.

Выбирайте вызов без контекста

Если серверной процедуре не нужны данные формы, ставьте &НаСервереБезКонтекста. Контекстный вызов передаёт весь реквизит Товары туда и обратно, даже если процедура его не трогает. На форме с тремя таблицами по тысяче строк это заметно.

Объявляйте параметры как Знач

Параметр серверной процедуры без Знач после вызова возвращается на клиент: платформа сериализует его повторно, чтобы клиент увидел изменения. Если массив на 5 000 элементов уходит на сервер только для чтения, пишите Функция Обработать(Знач Данные) — обратно он не поедет.

Не дробите вызовы

Цикл на клиенте, который на каждой строке вызывает сервер, — главный враг скорости. 500 строк — 500 вызовов. Соберите всё, что нужно, за один вызов и верните одним пакетом.

Порции для больших объёмов

Если пользователю надо показать десятки тысяч строк, не грузите их в реквизит целиком. Либо выводите по 500–1000 строк с кнопкой «Показать ещё», либо используйте динамический список (о нём ниже).

Перед тем как отдать код на проверку
Выполнено: 0 / 6

Ошибки при передаче таблицы и как их найти

Ошибки сериализации проявляются только при выполнении: синтаксический контроль в Конфигураторе их не ловит. Поэтому проверяйте форму в режиме 1С:Предприятия, а лучше в тонком клиенте и в браузере.

Диагностика: выберите симптом

Как увидеть, сколько данных уходит на клиент

Запустите 1С:Предприятие с параметром командной строки /DisplayPerformance. В окне программы появятся показатели: число серверных вызовов и объём переданных данных. Нажмите кнопку, сравните цифры до и после правки — так сразу видно, помог ли переход на &НаСервереБезКонтекста или Знач.

В отладчике поставьте точку останова в клиентской процедуре сразу после серверного вызова и посмотрите, что пришло. Если вместо ожидаемой коллекции Неопределено или ошибка — проблема в упаковке на сервере, а не в клиентском коде.

Когда таблица значений вообще не нужна

Часто задача «передать таблицу на клиент» — следствие неудачной архитектуры. Прежде чем упаковывать данные, проверьте, нельзя ли обойтись без этого.

Динамический список. Если пользователю надо показать результат запроса, добавьте на форму реквизит типа ДинамическийСписок с произвольным запросом. Платформа сама подгружает строки порциями при прокрутке, и вы не передаёте на клиент ни одной лишней строки.

Табличный документ. Для отчётных выводов, которые не редактируют, подходит ТабличныйДокумент. Его формируют на сервере, а на клиенте он отображается целиком, с оформлением.

Обработка на сервере целиком. Если клиенту нужен итог — сумма, количество, флаг «есть ошибки», — посчитайте его на сервере и верните одно число или структуру. Таблица в 10 000 строк ради одной суммы на клиенте — лишняя нагрузка.

Колонки заранее неизвестны. Когда таблица строится по произвольному запросу, реквизит можно создать программно через ИзменитьРеквизиты():

&НаСервере
Процедура ВывестиРезультат(ТекстЗапроса)
	ТЗ = Новый Запрос(ТекстЗапроса).Выполнить().Выгрузить();

	Добавляемые = Новый Массив;
	Добавляемые.Добавить(Новый РеквизитФормы("Результат", Новый ОписаниеТипов("ТаблицаЗначений")));
	Для Каждого Колонка Из ТЗ.Колонки Цикл
		ТипКолонки = Новый ОписаниеТипов(Колонка.ТипЗначения, , "Null");
		Добавляемые.Добавить(Новый РеквизитФормы(Колонка.Имя, ТипКолонки, "Результат"));
	КонецЦикла;
	ИзменитьРеквизиты(Добавляемые);

	ЭлементТаблица = Элементы.Добавить("Результат", Тип("ТаблицаФормы"));
	ЭлементТаблица.ПутьКДанным = "Результат";
	Для Каждого Колонка Из ТЗ.Колонки Цикл
		Поле = Элементы.Добавить("Результат" + Колонка.Имя, Тип("ПолеФормы"), ЭлементТаблица);
		Поле.Вид = ВидПоляФормы.ПолеВвода;
		Поле.ПутьКДанным = "Результат." + Колонка.Имя;
	КонецЦикла;

	ЗначениеВРеквизитФормы(ТЗ, "Результат");
КонецПроцедуры

Из типов колонок убран Null: запрос с левым соединением добавляет его в описание типов, а реквизиту формы он не нужен. При повторном вызове реквизит Результат уже существует — перед новым построением удалите его вторым параметром ИзменитьРеквизиты() и уберите старые элементы формы через Элементы.Удалить().

Частые вопросы

Можно ли передать дерево значений на клиент?
Напрямую нет, у дерева то же ограничение. Используйте реквизит формы типа ДеревоЗначений: на клиенте он станет ДанныеФормыДерево, а на сервере заполняется тем же методом ЗначениеВРеквизитФормы().
Можно ли передать на клиент результат запроса?
Нет. РезультатЗапроса и выборка существуют только на сервере. Выгрузите результат в таблицу и упакуйте её одним из способов: реквизит, массив структур, временное хранилище.
Чем ДанныеФормыКоллекция отличается от таблицы значений?
Это клиентское представление реквизита формы. Строки можно перебирать, добавлять и удалять, но нет методов группировки вроде Свернуть(), и в запрос её не передать. Сложную обработку делайте на сервере после РеквизитФормыВЗначение().
Как передать таблицу с клиента на сервер?
Если это реквизит формы, контекстный вызов &НаСервере доставит его сам — на сервере вызовите РеквизитФормыВЗначение(). Из клиентского кода без формы передайте массив структур и соберите таблицу на сервере.
Работает ли это в обычном приложении?
В обычных формах толстого клиента таблица значений существует на клиенте, и упаковывать её не нужно. Ограничения касаются управляемого приложения в тонком и веб-клиенте.