При работе в среде 1С:Предприятие 8.3 с управляемыми формами и, в частности, с тонким клиентом, многие разработчики сталкиваются с особенностями взаимодействия между клиентской и серверной частями платформы. Одна из таких распространенных ситуаций — попытка доступа к объекту типа ТаблицаЗначений, который находится внутри другого объекта (например, элемента СписокЗначений), напрямую на клиенте. Давайте подробно разберем, почему возникает ошибка "Поле объекта не обнаружено" в такой ситуации и как эффективно решить данную проблему.
Изначально, разработчик пытался получить доступ к вложенной ТаблицеЗначений, используя следующий код в процедуре, выполняемой на клиенте (&НаКлиенте):
Для Каждого эл Из СпискоЗначений Цикл
Если не эл.Значение.ТаблицаЗначений.Количество = 0 Тогда
// ...
КонецЕсли;
КонецЦикла;
При этом СписокЗначений является реквизитом формы и заполняется на сервере. В толстом клиенте этот код работал корректно, но в тонком клиенте возникала ошибка "Поле объекта не обнаружено". Выясним причину такого поведения.
Чтобы разобраться в корне проблемы, нам необходимо углубиться в принципы работы тонкого клиента 1С и различия между серверными объектами и клиентскими данными форм. Основной принцип заключается в том, что объект типа ТаблицаЗначений, созданный программно (например, с помощью оператора Новый ТаблицаЗначений()), является серверным объектом и не поддерживается напрямую в тонком клиенте. Это сделано для оптимизации передачи данных, снижения нагрузки на клиентскую часть и повышения производительности.
Когда вы объявляете реквизит формы с типом ТаблицаЗначений, платформа 1С автоматически преобразует его в специальный объект — ДанныеФормыКоллекция (Form Data Collection). Это происходит как на клиенте, так и на сервере в контексте формы. ДанныеФормыКоллекция — это клиент-совместимый объект, который позволяет работать с коллекциями данных в управляемой форме, предоставляя методы для добавления, удаления, обхода элементов и доступа по индексу, подобно обычной коллекции.
Строки ДанныеФормыКоллекция, в свою очередь, преобразуются в ДанныеФормыЭлементКоллекции. Именно с этими объектами вы можете работать в клиентском коде, получая доступ к их полям (колонкам таблицы).
Теперь давайте проанализируем конкретный случай автора. У нас есть СписокЗначений, который является реквизитом формы. Предположим, он был заполнен на сервере следующим образом:
// На сервере, при создании или обновлении данных
СписокЗначенийФормы = Новый СписокЗначений;
// Создаем структуру, которая содержит ТаблицуЗначений
СтруктураСДанными = Новый Структура;
СтруктураСДанными.Вставить("Идентификатор", "Пример");
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Колонка1");
ТЗ.Колонки.Добавить("Колонка2");
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.Колонка1 = "Значение1";
НоваяСтрока.Колонка2 = 123;
СтруктураСДанными.Вставить("ТаблицаЗначений", ТЗ); // Вкладываем ТаблицуЗначений
СписокЗначенийФормы.Добавить(СтруктураСДанными, "Элемент с ТЗ");
// Если сам элемент списка - это ТаблицаЗначений
ТЗ2 = Новый ТаблицаЗначений;
ТЗ2.Колонки.Добавить("ДругаяКолонка");
НоваяСтрока2 = ТЗ2.Добавить();
НоваяСтрока2.ДругаяКолонка = "ДругоеЗначение";
СписокЗначенийФормы.Добавить(ТЗ2, "Элемент - сама ТЗ");
// После этого СписокЗначенийФормы присваивается реквизиту формы.
// РеквизитФормыВЗначение и ЗначениеВРеквизитФормы используются для работы
// с реквизитами формы типа коллекций на сервере и клиенте.
ЗначениеВРеквизитФормы(СписокЗначенийФормы, "ИмяРеквизитаСпискаЗначений");
В этом сценарии, хотя сам реквизит формы СписокЗначений будет доступен на клиенте как ДанныеФормыКоллекция (если он был объявлен как реквизит формы), объекты, которые являются значениями его элементов (например, СтруктураСДанными или ТЗ2 из примера выше), сохранят свой тип и останутся серверными объектами, если они были созданы как таковые.
Когда вы пытаетесь обратиться к эл.Значение.ТаблицаЗначений на клиенте, где эл.Значение является серверным объектом (например, структурой, содержащей серверную ТаблицуЗначений), платформа 1С не может предоставить прямой доступ к вложенному серверному объекту ТаблицаЗначений. Для клиента эл.Значение представляет собой некую "виртуальную" структуру, но ее поля, которые ссылаются на серверные объекты, не будут доступны для прямого обращения на клиенте. Именно поэтому вы получаете ошибку "Поле объекта не обнаружено". Тонкий клиент просто "не знает", что внутри этой структуры есть поле с именем "ТаблицаЗначений", которое ведет на полноценный серверный объект.
Существует несколько подходов к решению данной проблемы, в зависимости от того, что именно вам нужно сделать с ТаблицейЗначений на клиенте.
Если данные из вложенной ТаблицыЗначений нужны на клиенте только для отображения или простой итерации (например, для заполнения таблицы на форме или вывода сообщений), мы можем преобразовать их на сервере в клиент-совместимый формат, такой как МассивСтруктур или МассивМассивов. Этот подход позволяет передать "плоские" данные, с которыми тонкий клиент умеет работать.
Рассмотрим по шагам, как это можно реализовать:
СписокЗначений (полученный из реквизита формы) и преобразовывать каждую вложенную ТаблицуЗначений в МассивСтруктур.Массив, содержащий преобразованные данные, который затем можно использовать на клиенте.Массив для доступа к данным.Посмотрим на пример кода:
// В модуле формы или в общем модуле, вызванном с сервера
&НаСервере
Функция ПолучитьДанныеДляКлиентаИзСписка(ИмяРеквизитаСпискаЗначений) Экспорт
СписокЗначенийСервер = РеквизитФормыВЗначение(ИмяРеквизитаСпискаЗначений, Тип("СписокЗначений"));
МассивРезультатовДляКлиента = Новый Массив;
Для Каждого ЭлементСписка Из СписокЗначенийСервер Цикл
// Проверяем, является ли значение элемента списком или структурой, содержащей ТЗ
Если ТипЗнч(ЭлементСписка.Значение) = Тип("Структура") Тогда
// Если Значение - структура, и в ней есть поле "ТаблицаЗначений"
Если ЭлементСписка.Значение.Свойство("ТаблицаЗначений") Тогда
ВложеннаяТЗ = ЭлементСписка.Значение.ТаблицаЗначений;
Если ТипЗнч(ВложеннаяТЗ) = Тип("ТаблицаЗначений") Тогда
// Преобразуем ТаблицуЗначений в МассивСтруктур
МассивСтрокТЗ = Новый Массив;
Для Каждого СтрокаТЗ Из ВложеннаяТЗ Цикл
СтруктураСтроки = Новый Структура;
Для Каждого КолонкаТЗ Из ВложеннаяТЗ.Колонки Цикл
Попытка
СтруктураСтроки.Вставить(КолонкаТЗ.Имя, СтрокаТЗ[КолонкаТЗ.Имя]);
Исключение
// Если поле недоступно или не может быть конвертировано
СтруктураСтроки.Вставить(КолонкаТЗ.Имя, Неопределено);
КонецПопытки;
КонецЦикла;
МассивСтрокТЗ.Добавить(СтруктураСтроки);
КонецЦикла;
МассивРезультатовДляКлиента.Добавить(МассивСтрокТЗ);
Иначе
// Если поле "ТаблицаЗначений" не является ТаблицейЗначений
МассивРезультатовДляКлиента.Добавить(ЭлементСписка.Значение);
КонецЕсли;
Иначе
// Если структура не содержит ТаблицуЗначений, добавляем ее как есть
МассивРезультатовДляКлиента.Добавить(ЭлементСписка.Значение);
КонецЕсли;
ИначеЕсли ТипЗнч(ЭлементСписка.Значение) = Тип("ТаблицаЗначений") Тогда
// Если сам элемент списка является ТаблицейЗначений
МассивСтрокТЗ = Новый Массив;
Для Каждого СтрокаТЗ Из ЭлементСписка.Значение Цикл
СтруктураСтроки = Новый Структура;
Для Каждого КолонкаТЗ Из ЭлементСписка.Значение.Колонки Цикл
Попытка
СтруктураСтроки.Вставить(КолонкаТЗ.Имя, СтрокаТЗ[КолонкаТЗ.Имя]);
Исключение
СтруктураСтроки.Вставить(КолонкаТЗ.Имя, Неопределено);
КонецПопытки;
КонецКаждого;
МассивСтрокТЗ.Добавить(СтруктураСтроки);
КонецЦикла;
МассивРезультатовДляКлиента.Добавить(МассивСтрокТЗ);
Иначе
// Обработка других типов значений, если необходимо
МассивРезультатовДляКлиента.Добавить(ЭлементСписка.Значение);
КонецЕсли;
КонецЦикла;
Возврат МассивРезультатовДляКлиента;
КонецФункции
// На клиенте
&НаКлиенте
Процедура ОбработатьДанныеССервера()
// Вызываем серверную функцию для получения данных
МассивДанныхДляКлиента = ПолучитьДанныеДляКлиентаИзСписка("ИмяРеквизитаСпискаЗначений");
Для Каждого ДанныеЭлемента Из МассивДанныхДляКлиента Цикл
Если ТипЗнч(ДанныеЭлемента) = Тип("Массив") Тогда
// Это наш МассивСтруктур, переданный вместо ТаблицыЗначений
Если ДанныеЭлемента.Количество() > 0 Тогда
Сообщить("Внутри элемента есть МассивСтруктур с " + ДанныеЭлемента.Количество() + " строками.");
Для Каждого СтруктураСтроки Из ДанныеЭлемента Цикл
// Теперь мы можем безопасно работать с данными на клиенте
// Например, получить значение по имени колонки
Если СтруктураСтроки.Свойство("Колонка1") Тогда
Сообщить("Значение 'Колонка1': " + СтруктураСтроки.Колонка1);
КонецЕсли;
КонецЦикла;
Иначе
Сообщить("МассивСтруктур пуст.");
КонецЕсли;
Иначе
Сообщить("Значение элемента: " + Строка(ДанныеЭлемента));
КонецЕсли;
КонецЦикла;
КонецПроцедуры
В этом примере мы используем РеквизитФормыВЗначение() для получения серверного представления реквизита формы, обрабатываем его на сервере и возвращаем клиент-совместимые данные.
Если вам требуется использовать полную функциональность ТаблицыЗначений (такую как выполнение запросов по ней, фильтрация, сортировка, выгрузка в табличный документ или какие-либо сложные манипуляции), то наиболее правильным решением будет выполнение всех этих операций на сервере. Клиент должен лишь инициировать вызов серверной процедуры, которая получит ТаблицуЗначений, выполнит необходимые действия и, при необходимости, вернет результат (например, булево значение, число или упрощенный массив данных) обратно на клиент.
Рассмотрим пошаговый план:
СписокЗначений, передаем его на сервер с помощью РеквизитФормыВЗначение().СписокЗначений, обходит его элементы, извлекает ТаблицыЗначений и выполняет с ними все необходимые действия.Пример кода, реализующего проверку количества строк вложенных ТаблицЗначений на сервере:
// На клиенте
&НаКлиенте
Процедура ПроверитьВсеВложенныеТаблицыЗначений()
// Передаем имя реквизита формы на сервер
КоличествоНепустыхВложенныхТЗ = ПроверитьНаСервереВложенныеТЗ("ИмяРеквизитаСпискаЗначений");
Если КоличествоНепустыхВложенныхТЗ > 0 Тогда
Сообщить("Найдено " + КоличествоНепустыхВложенныхТЗ + " непустых вложенных ТаблицЗначений.");
Иначе
Сообщить("Все вложенные ТаблицыЗначений пусты или отсутствуют.");
КонецЕсли;
КонецПроцедуры
// На сервере
&НаСервере
Функция ПроверитьНаСервереВложенныеТЗ(ИмяРеквизитаСпискаЗначений) Экспорт
СписокЗначенийСервер = РеквизитФормыВЗначение(ИмяРеквизитаСпискаЗначений, Тип("СписокЗначений"));
КоличествоНепустых = 0;
Для Каждого ЭлементСписка Из СписокЗначенийСервер Цикл
// Проверяем, является ли значение элемента списком или структурой, содержащей ТЗ
Если ТипЗнч(ЭлементСписка.Значение) = Тип("Структура") Тогда
// Если Значение - структура, и в ней есть поле "ТаблицаЗначений"
Если ЭлементСписка.Значение.Свойство("ТаблицаЗначений") Тогда
ВложеннаяТЗ = ЭлементСписка.Значение.ТаблицаЗначений;
Если ТипЗнч(ВложеннаяТЗ) = Тип("ТаблицаЗначений") Тогда
Если ВложеннаяТЗ.Количество() > 0 Тогда
КоличествоНепустых = КоличествоНепустых + 1;
КонецЕсли;
КонецЕсли;
КонецЕсли;
ИначеЕсли ТипЗнч(ЭлементСписка.Значение) = Тип("ТаблицаЗначений") Тогда
// Если сам элемент списка является ТаблицейЗначений
Если ЭлементСписка.Значение.Количество() > 0 Тогда
КоличествоНепустых = КоличествоНепустых + 1;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Возврат КоличествоНепустых;
КонецФункции
Обратите внимание на использование функции РеквизитФормыВЗначение(). Она позволяет получить объект серверного типа (в данном случае СписокЗначений) из реквизита формы, который на клиенте представлен как ДанныеФормыКоллекция. Таким образом, на сервере мы работаем с настоящими серверными объектами.
Это решение не относится напрямую к *вложенной* ТаблицеЗначений в СписокЗначений, но важно для общего понимания. Если бы у вас был реквизит формы непосредственно типа ТаблицаЗначений (а не СписокЗначений, в котором лежат ТаблицыЗначений), то вы могли бы работать с ним на клиенте как с ДанныеФормыКоллекция.
В этом случае, на клиенте вы бы обращались к этому реквизиту напрямую:
// Допустим, у нас есть реквизит формы с именем "МояТаблицаДанных" типа ТаблицаЗначений
// На самом деле, это ДанныеФормыКоллекция на клиенте и в контексте формы на сервере.
&НаКлиенте
Процедура РаботаСДаннымиФормыКоллекция()
Если МояТаблицаДанных.Количество() > 0 Тогда
Сообщить("В реквизите формы 'МояТаблицаДанных' есть " + МояТаблицаДанных.Количество() + " строк.");
Для Каждого СтрокаКоллекции Из МояТаблицаДанных Цикл
// Доступ к полям строки: СтрокаКоллекции.ИмяКолонки
// Например, если есть колонка "Поле1"
Сообщить("Значение поля 'Поле1': " + СтрокаКоллекции.Поле1);
КонецЦикла;
Иначе
Сообщить("Реквизит формы 'МояТаблицаДанных' пуст.");
КонецЕсли;
КонецПроцедуры
Если же вам нужно получить именно объект ТаблицаЗначений из реквизита формы, который является ДанныеФормыКоллекция, вы можете сделать это на сервере:
// На сервере
&НаСервере
Функция ПолучитьТЗИзРеквизитаФормы()
// "МояТаблицаДанных" - имя реквизита формы
// Получаем ТаблицуЗначений из ДанныеФормыКоллекция
ТЗ = РеквизитФормыВЗначение("МояТаблицаДанных", Тип("ТаблицаЗначений"));
// Или, в контексте формы, если МояТаблицаДанных является реквизитом формы:
// ТЗ = МояТаблицаДанных.Выгрузить(); // Этот метод работает в серверном контексте формы
Возврат ТЗ;
КонецФункции
ДанныеФормыЭлементКоллекции на клиенте может быть невозможно. На клиенте может быть доступно только представление данных (например, наименование или код), а не сам объект целиком. Для получения полной информации об объекте, опять же, потребуется серверный вызов.РеквизитФормыВЗначение() для получения серверного представления реквизита формы и ЗначениеВРеквизитФормы() для обратной записи изменений из серверного объекта в реквизит формы.Проблема доступа к вложенной ТаблицеЗначений в тонком клиенте 1С является следствием архитектурных особенностей платформы. Критически важно понимать различие между серверным объектом ТаблицаЗначений и клиентским представлением данных в виде ДанныеФормыКоллекция. Если вам необходимо работать с полноценной ТаблицейЗначений, делайте это на сервере. Если данные нужны только для отображения на клиенте, преобразуйте их в клиент-совместимые типы, такие как МассивСтруктур. Осознанное применение этих принципов позволит вам создавать производительные и надежные решения в управляемых формах 1С.