Как получить доступ к вложенной ТаблицеЗначений элемента СпискаЗначений в тонком клиенте 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе в среде 1С:Предприятие 8.3 с управляемыми формами и, в частности, с тонким клиентом, многие разработчики сталкиваются с особенностями взаимодействия между клиентской и серверной частями платформы. Одна из таких распространенных ситуаций — попытка доступа к объекту типа ТаблицаЗначений, который находится внутри другого объекта (например, элемента СписокЗначений), напрямую на клиенте. Давайте подробно разберем, почему возникает ошибка "Поле объекта не обнаружено" в такой ситуации и как эффективно решить данную проблему.

Изначально, разработчик пытался получить доступ к вложенной ТаблицеЗначений, используя следующий код в процедуре, выполняемой на клиенте (&НаКлиенте):


Для Каждого эл Из СпискоЗначений Цикл
    Если не эл.Значение.ТаблицаЗначений.Количество = 0 Тогда
        // ...
    КонецЕсли;
КонецЦикла;

При этом СписокЗначений является реквизитом формы и заполняется на сервере. В толстом клиенте этот код работал корректно, но в тонком клиенте возникала ошибка "Поле объекта не обнаружено". Выясним причину такого поведения.

Понимание архитектуры тонкого клиента и объектов данных

Чтобы разобраться в корне проблемы, нам необходимо углубиться в принципы работы тонкого клиента 1С и различия между серверными объектами и клиентскими данными форм. Основной принцип заключается в том, что объект типа ТаблицаЗначений, созданный программно (например, с помощью оператора Новый ТаблицаЗначений()), является серверным объектом и не поддерживается напрямую в тонком клиенте. Это сделано для оптимизации передачи данных, снижения нагрузки на клиентскую часть и повышения производительности.

Когда вы объявляете реквизит формы с типом ТаблицаЗначений, платформа 1С автоматически преобразует его в специальный объект — ДанныеФормыКоллекция (Form Data Collection). Это происходит как на клиенте, так и на сервере в контексте формы. ДанныеФормыКоллекция — это клиент-совместимый объект, который позволяет работать с коллекциями данных в управляемой форме, предоставляя методы для добавления, удаления, обхода элементов и доступа по индексу, подобно обычной коллекции.

Строки ДанныеФормыКоллекция, в свою очередь, преобразуются в ДанныеФормыЭлементКоллекции. Именно с этими объектами вы можете работать в клиентском коде, получая доступ к их полям (колонкам таблицы).

Особенности работы с вложенными коллекциями в тонком клиенте

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


// На сервере, при создании или обновлении данных
СписокЗначенийФормы = Новый СписокЗначений;
// Создаем структуру, которая содержит ТаблицуЗначений
СтруктураСДанными = Новый Структура;
СтруктураСДанными.Вставить("Идентификатор", "Пример");
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Колонка1");
ТЗ.Колонки.Добавить("Колонка2");
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.Колонка1 = "Значение1";
НоваяСтрока.Колонка2 = 123;
СтруктураСДанными.Вставить("ТаблицаЗначений", ТЗ); // Вкладываем ТаблицуЗначений
СписокЗначенийФормы.Добавить(СтруктураСДанными, "Элемент с ТЗ");

// Если сам элемент списка - это ТаблицаЗначений
ТЗ2 = Новый ТаблицаЗначений;
ТЗ2.Колонки.Добавить("ДругаяКолонка");
НоваяСтрока2 = ТЗ2.Добавить();
НоваяСтрока2.ДругаяКолонка = "ДругоеЗначение";
СписокЗначенийФормы.Добавить(ТЗ2, "Элемент - сама ТЗ");

// После этого СписокЗначенийФормы присваивается реквизиту формы.
// РеквизитФормыВЗначение и ЗначениеВРеквизитФормы используются для работы
// с реквизитами формы типа коллекций на сервере и клиенте.
ЗначениеВРеквизитФормы(СписокЗначенийФормы, "ИмяРеквизитаСпискаЗначений");

В этом сценарии, хотя сам реквизит формы СписокЗначений будет доступен на клиенте как ДанныеФормыКоллекция (если он был объявлен как реквизит формы), объекты, которые являются значениями его элементов (например, СтруктураСДанными или ТЗ2 из примера выше), сохранят свой тип и останутся серверными объектами, если они были созданы как таковые.

Когда вы пытаетесь обратиться к эл.Значение.ТаблицаЗначений на клиенте, где эл.Значение является серверным объектом (например, структурой, содержащей серверную ТаблицуЗначений), платформа 1С не может предоставить прямой доступ к вложенному серверному объекту ТаблицаЗначений. Для клиента эл.Значение представляет собой некую "виртуальную" структуру, но ее поля, которые ссылаются на серверные объекты, не будут доступны для прямого обращения на клиенте. Именно поэтому вы получаете ошибку "Поле объекта не обнаружено". Тонкий клиент просто "не знает", что внутри этой структуры есть поле с именем "ТаблицаЗначений", которое ведет на полноценный серверный объект.

Решения и обходные пути

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

Решение 1: Передача данных на клиент в виде, поддерживаемом тонким клиентом

Если данные из вложенной ТаблицыЗначений нужны на клиенте только для отображения или простой итерации (например, для заполнения таблицы на форме или вывода сообщений), мы можем преобразовать их на сервере в клиент-совместимый формат, такой как МассивСтруктур или МассивМассивов. Этот подход позволяет передать "плоские" данные, с которыми тонкий клиент умеет работать.

Рассмотрим по шагам, как это можно реализовать:

  1. На сервере: Создайте функцию, которая будет принимать ваш СписокЗначений (полученный из реквизита формы) и преобразовывать каждую вложенную ТаблицуЗначений в МассивСтруктур.
  2. Возвращение данных: Эта функция должна вернуть новый Массив, содержащий преобразованные данные, который затем можно использовать на клиенте.
  3. На клиенте: Используйте полученный Массив для доступа к данным.

Посмотрим на пример кода:


// В модуле формы или в общем модуле, вызванном с сервера
&НаСервере
Функция ПолучитьДанныеДляКлиентаИзСписка(ИмяРеквизитаСпискаЗначений) Экспорт
    СписокЗначенийСервер = РеквизитФормыВЗначение(ИмяРеквизитаСпискаЗначений, Тип("СписокЗначений"));

    МассивРезультатовДляКлиента = Новый Массив;

    Для Каждого ЭлементСписка Из СписокЗначенийСервер Цикл
        // Проверяем, является ли значение элемента списком или структурой, содержащей ТЗ
        Если ТипЗнч(ЭлементСписка.Значение) = Тип("Структура") Тогда
            // Если Значение - структура, и в ней есть поле "ТаблицаЗначений"
            Если ЭлементСписка.Значение.Свойство("ТаблицаЗначений") Тогда
                ВложеннаяТЗ = ЭлементСписка.Значение.ТаблицаЗначений;
                Если ТипЗнч(ВложеннаяТЗ) = Тип("ТаблицаЗначений") Тогда
                    // Преобразуем ТаблицуЗначений в МассивСтруктур
                    МассивСтрокТЗ = Новый Массив;
                    Для Каждого СтрокаТЗ Из ВложеннаяТЗ Цикл
                        СтруктураСтроки = Новый Структура;
                        Для Каждого КолонкаТЗ Из ВложеннаяТЗ.Колонки Цикл
                            Попытка
                                СтруктураСтроки.Вставить(КолонкаТЗ.Имя, СтрокаТЗ[КолонкаТЗ.Имя]);
                            Исключение
                                // Если поле недоступно или не может быть конвертировано
                                СтруктураСтроки.Вставить(КолонкаТЗ.Имя, Неопределено);
                            КонецПопытки;
                        КонецЦикла;
                        МассивСтрокТЗ.Добавить(СтруктураСтроки);
                    КонецЦикла;
                    МассивРезультатовДляКлиента.Добавить(МассивСтрокТЗ);
                Иначе
                    // Если поле "ТаблицаЗначений" не является ТаблицейЗначений
                    МассивРезультатовДляКлиента.Добавить(ЭлементСписка.Значение);
                КонецЕсли;
            Иначе
                // Если структура не содержит ТаблицуЗначений, добавляем ее как есть
                МассивРезультатовДляКлиента.Добавить(ЭлементСписка.Значение);
            КонецЕсли;
        ИначеЕсли ТипЗнч(ЭлементСписка.Значение) = Тип("ТаблицаЗначений") Тогда
            // Если сам элемент списка является ТаблицейЗначений
            МассивСтрокТЗ = Новый Массив;
            Для Каждого СтрокаТЗ Из ЭлементСписка.Значение Цикл
                СтруктураСтроки = Новый Структура;
                Для Каждого КолонкаТЗ Из ЭлементСписка.Значение.Колонки Цикл
                    Попытка
                        СтруктураСтроки.Вставить(КолонкаТЗ.Имя, СтрокаТЗ[КолонкаТЗ.Имя]);
                    Исключение
                        СтруктураСтроки.Вставить(КолонкаТЗ.Имя, Неопределено);
                    КонецПопытки;
                КонецКаждого;
                МассивСтрокТЗ.Добавить(СтруктураСтроки);
            КонецЦикла;
            МассивРезультатовДляКлиента.Добавить(МассивСтрокТЗ);
        Иначе
            // Обработка других типов значений, если необходимо
            МассивРезультатовДляКлиента.Добавить(ЭлементСписка.Значение);
        КонецЕсли;
    КонецЦикла;

    Возврат МассивРезультатовДляКлиента;
КонецФункции

// На клиенте
&НаКлиенте
Процедура ОбработатьДанныеССервера()
    // Вызываем серверную функцию для получения данных
    МассивДанныхДляКлиента = ПолучитьДанныеДляКлиентаИзСписка("ИмяРеквизитаСпискаЗначений");

    Для Каждого ДанныеЭлемента Из МассивДанныхДляКлиента Цикл
        Если ТипЗнч(ДанныеЭлемента) = Тип("Массив") Тогда
            // Это наш МассивСтруктур, переданный вместо ТаблицыЗначений
            Если ДанныеЭлемента.Количество() > 0 Тогда
                Сообщить("Внутри элемента есть МассивСтруктур с " + ДанныеЭлемента.Количество() + " строками.");
                Для Каждого СтруктураСтроки Из ДанныеЭлемента Цикл
                    // Теперь мы можем безопасно работать с данными на клиенте
                    // Например, получить значение по имени колонки
                    Если СтруктураСтроки.Свойство("Колонка1") Тогда
                        Сообщить("Значение 'Колонка1': " + СтруктураСтроки.Колонка1);
                    КонецЕсли;
                КонецЦикла;
            Иначе
                Сообщить("МассивСтруктур пуст.");
            КонецЕсли;
        Иначе
            Сообщить("Значение элемента: " + Строка(ДанныеЭлемента));
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

В этом примере мы используем РеквизитФормыВЗначение() для получения серверного представления реквизита формы, обрабатываем его на сервере и возвращаем клиент-совместимые данные.

Решение 2: Выполнение всех операций с ТаблицейЗначений на сервере

Если вам требуется использовать полную функциональность ТаблицыЗначений (такую как выполнение запросов по ней, фильтрация, сортировка, выгрузка в табличный документ или какие-либо сложные манипуляции), то наиболее правильным решением будет выполнение всех этих операций на сервере. Клиент должен лишь инициировать вызов серверной процедуры, которая получит ТаблицуЗначений, выполнит необходимые действия и, при необходимости, вернет результат (например, булево значение, число или упрощенный массив данных) обратно на клиент.

Рассмотрим пошаговый план:

  1. На клиенте: Инициируем вызов серверной процедуры (например, по кнопке или при изменении данных).
  2. Передача данных на сервер: Если для работы серверной процедуре нужен весь СписокЗначений, передаем его на сервер с помощью РеквизитФормыВЗначение().
  3. На сервере: Серверная процедура получает СписокЗначений, обходит его элементы, извлекает ТаблицыЗначений и выполняет с ними все необходимые действия.
  4. Возврат результата: Серверная процедура возвращает на клиент только необходимый, клиент-совместимый результат (например, количество строк, флаг успеха/неудачи и т.д.).

Пример кода, реализующего проверку количества строк вложенных ТаблицЗначений на сервере:


// На клиенте
&НаКлиенте
Процедура ПроверитьВсеВложенныеТаблицыЗначений()
    // Передаем имя реквизита формы на сервер
    КоличествоНепустыхВложенныхТЗ = ПроверитьНаСервереВложенныеТЗ("ИмяРеквизитаСпискаЗначений");

    Если КоличествоНепустыхВложенныхТЗ > 0 Тогда
        Сообщить("Найдено " + КоличествоНепустыхВложенныхТЗ + " непустых вложенных ТаблицЗначений.");
    Иначе
        Сообщить("Все вложенные ТаблицыЗначений пусты или отсутствуют.");
    КонецЕсли;
КонецПроцедуры

// На сервере
&НаСервере
Функция ПроверитьНаСервереВложенныеТЗ(ИмяРеквизитаСпискаЗначений) Экспорт
    СписокЗначенийСервер = РеквизитФормыВЗначение(ИмяРеквизитаСпискаЗначений, Тип("СписокЗначений"));

    КоличествоНепустых = 0;
    Для Каждого ЭлементСписка Из СписокЗначенийСервер Цикл
        // Проверяем, является ли значение элемента списком или структурой, содержащей ТЗ
        Если ТипЗнч(ЭлементСписка.Значение) = Тип("Структура") Тогда
            // Если Значение - структура, и в ней есть поле "ТаблицаЗначений"
            Если ЭлементСписка.Значение.Свойство("ТаблицаЗначений") Тогда
                ВложеннаяТЗ = ЭлементСписка.Значение.ТаблицаЗначений;
                Если ТипЗнч(ВложеннаяТЗ) = Тип("ТаблицаЗначений") Тогда
                    Если ВложеннаяТЗ.Количество() > 0 Тогда
                        КоличествоНепустых = КоличествоНепустых + 1;
                    КонецЕсли;
                КонецЕсли;
            КонецЕсли;
        ИначеЕсли ТипЗнч(ЭлементСписка.Значение) = Тип("ТаблицаЗначений") Тогда
            // Если сам элемент списка является ТаблицейЗначений
            Если ЭлементСписка.Значение.Количество() > 0 Тогда
                КоличествоНепустых = КоличествоНепустых + 1;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    Возврат КоличествоНепустых;
КонецФункции

Обратите внимание на использование функции РеквизитФормыВЗначение(). Она позволяет получить объект серверного типа (в данном случае СписокЗначений) из реквизита формы, который на клиенте представлен как ДанныеФормыКоллекция. Таким образом, на сервере мы работаем с настоящими серверными объектами.

Решение 3: Работа с ДанныеФормыКоллекция, если ТаблицаЗначений является непосредственно реквизитом формы

Это решение не относится напрямую к *вложенной* ТаблицеЗначений в СписокЗначений, но важно для общего понимания. Если бы у вас был реквизит формы непосредственно типа ТаблицаЗначений (а не СписокЗначений, в котором лежат ТаблицыЗначений), то вы могли бы работать с ним на клиенте как с ДанныеФормыКоллекция.

В этом случае, на клиенте вы бы обращались к этому реквизиту напрямую:


// Допустим, у нас есть реквизит формы с именем "МояТаблицаДанных" типа ТаблицаЗначений
// На самом деле, это ДанныеФормыКоллекция на клиенте и в контексте формы на сервере.
&НаКлиенте
Процедура РаботаСДаннымиФормыКоллекция()
    Если МояТаблицаДанных.Количество() > 0 Тогда
        Сообщить("В реквизите формы 'МояТаблицаДанных' есть " + МояТаблицаДанных.Количество() + " строк.");
        Для Каждого СтрокаКоллекции Из МояТаблицаДанных Цикл
            // Доступ к полям строки: СтрокаКоллекции.ИмяКолонки
            // Например, если есть колонка "Поле1"
            Сообщить("Значение поля 'Поле1': " + СтрокаКоллекции.Поле1);
        КонецЦикла;
    Иначе
        Сообщить("Реквизит формы 'МояТаблицаДанных' пуст.");
    КонецЕсли;
КонецПроцедуры

Если же вам нужно получить именно объект ТаблицаЗначений из реквизита формы, который является ДанныеФормыКоллекция, вы можете сделать это на сервере:


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

Важные рекомендации по работе с данными в тонком клиенте

  1. Оптимизация для больших объемов данных: Для больших коллекций данных (например, от 20 строк и более) обход и поиск строк рекомендуется выполнять на сервере. Это помогает избежать лишних неявных серверных вызовов, которые могут возникать при постраничной передаче данных между клиентом и сервером, что значительно снижает производительность.
  2. Избегайте неявных серверных вызовов: Любое обращение к свойствам или методам серверного объекта из клиентского кода может привести к неявному серверному вызову. Старайтесь минимизировать такие операции.
  3. Ссылки на объекты: При работе с вложенными объектами, такими как ссылки на элементы справочников или документы, напрямую обращаться к их полям из ДанныеФормыЭлементКоллекции на клиенте может быть невозможно. На клиенте может быть доступно только представление данных (например, наименование или код), а не сам объект целиком. Для получения полной информации об объекте, опять же, потребуется серверный вызов.
  4. Используйте функции для преобразования: Активно применяйте функции РеквизитФормыВЗначение() для получения серверного представления реквизита формы и ЗначениеВРеквизитФормы() для обратной записи изменений из серверного объекта в реквизит формы.

Заключение

Проблема доступа к вложенной ТаблицеЗначений в тонком клиенте 1С является следствием архитектурных особенностей платформы. Критически важно понимать различие между серверным объектом ТаблицаЗначений и клиентским представлением данных в виде ДанныеФормыКоллекция. Если вам необходимо работать с полноценной ТаблицейЗначений, делайте это на сервере. Если данные нужны только для отображения на клиенте, преобразуйте их в клиент-совместимые типы, такие как МассивСтруктур. Осознанное применение этих принципов позволит вам создавать производительные и надежные решения в управляемых формах 1С.

← На главную