Как исправить ошибку «Отсутствует отображение для типа 'ДанныеФормыЭлементКоллекции'» в 1С 8.3

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

При переходе на управляемые формы в 1С 8.3 начинающие разработчики часто сталкиваются с непониманием клиент-серверной архитектуры. Одна из самых распространенных ошибок при попытке заполнить данные в строке табличной части — это «Ошибка отображения типов: Отсутствует отображение для типа 'ДанныеФормыЭлементКоллекции'». В этой статье мы подробно разберем причины возникновения этой проблемы, проанализируем неверные подходы и рассмотрим два правильных способа решения в соответствии со стандартами разработки фирмы «1С».

Выясняем причину: почему возникает ошибка?

Для начала давайте разберемся, что представляет собой тип ДанныеФормыЭлементКоллекции. В управляемом приложении этот тип является клиентским «отражением» строки таблицы на форме. Он предназначен исключительно для визуализации данных в интерфейсе на стороне клиента и тесно связан со свойствами самого элемента управления на форме.

Когда мы пытаемся передать переменную, содержащую текущую строку табличной части (например, полученную через Элементы.ИмяТаблицы.ТекущиеДанные), в процедуру или функцию на сервере, платформа выполняет процесс сериализации. Сериализация — это преобразование объекта в XML/XDTO-формат для его передачи по сети между клиентом и сервером. Поскольку тип ДанныеФормыЭлементКоллекции жестко привязан к контексту отображения формы на клиенте, платформа физически не может передать его на сервер «как есть». В результате этого процесса мы и получаем ошибку преобразования XDTO.

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

Анализируем типичные ошибки в коде

Рассмотрим код, который часто пытаются написать начинающие разработчики:


&НаКлиенте
Процедура НоменклатураПриИзменении(Элемент)
    СтрокаТабличнойЧасти = Элементы.ПМ.ТекущиеДанные;
    // Попытка передать строку ТЧ напрямую на сервер в общий модуль
    ПеремещДок.ПеремещМат(СтрокаТабличнойЧасти); 
КонецПроцедуры

В данном случае переменная СтрокаТабличнойЧасти как раз имеет тип ДанныеФормыЭлементКоллекции. Попытка передать ее в процедуру общего модуля гарантированно вызовет ошибку отображения типов. Более того, выполнение запросов или работа с таблицами значений непосредственно на клиенте также невозможны, так как эти объекты существуют исключительно на стороне сервера.

Теперь рассмотрим правильные архитектурные решения этой задачи.

Решение 1. Использование легкой структуры параметров (Рекомендуемый вариант)

Согласно стандартам разработки «1С:Предприятия», наиболее эффективным и быстрым способом является использование внеконтекстных серверных вызовов. Для этого мы не передаем саму строку, а собираем нужные для запроса параметры в простую Структура на клиенте, передаем ее в функцию с директивой &НаСервереБезКонтекста, получаем результат обратно в виде структуры и заполняем строку ТЧ прямо на клиенте.

Давайте разберем этот процесс по шагам.

Шаг 1. Пишем клиентскую процедуру в модуле формы

На клиенте мы получаем текущую строку нашей табличной части, подготавливаем структуру параметров и вызываем серверную функцию.


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

Шаг 2. Реализуем внеконтекстную функцию на сервере

Создаем функцию с директивой &НаСервереБезКонтекста. Она выполнит запрос к базе данных и вернет структуру с ценой и остатком обратно на клиент.


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

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

Решение 2. Использование контекстного вызова сервера и идентификатора строки

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

Поскольку контекстный вызов передает всю форму на сервер целиком, мы имеем прямой доступ к данным объекта через Объект. Передавать строку ТЧ параметром все еще нельзя, но мы можем передать ее уникальный идентификатор.

Рассмотрим, как реализовать этот подход:


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

&НаСервере
Процедура ЗаполнитьСтрокуТЧНаСервере(ИдентификаторСтроки)
    
    // Находим строку в табличной части Объект по полученному идентификатору
    СтрокаТЧ = Объект.ПМ.НайтиПоИдентификатору(ИдентификаторСтроки);
    
    Если СтрокаТЧ <> Неопределено Тогда
        
        // Здесь мы находимся на сервере и можем напрямую работать с базой данных
        // Выполняем аналогичный запрос к регистрам сведений и накопления
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ
            |	ЕСТЬNULL(ЦеныСрезПоследних.Цена, 0) КАК ЦенаЗаЕдиницу,
            |	ЕСТЬNULL(ОстаткиМатериаловОстатки.КоличествоОстаток, 0) КАК ОстатокНаСкладе
            |ИЗ
            |	РегистрСведений.Цены.СрезПоследних(&Дата, Номенклатура = &Номенклатура) КАК ЦеныСрезПоследних
            |	ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиМатериалов.Остатки(&Дата, Материал = &Номенклатура) КАК ОстаткиМатериаловОстатки
            |		ПО ОстаткиМатериаловОстатки.Материал = ЦеныСрезПоследних.Номенклатура";
            
        Запрос.УстановитьПараметр("Дата", Объект.Дата);
        Запрос.УстановитьПараметр("Номенклатура", СтрокаТЧ.Номенклатура);
        
        Выборка = Запрос.Выполнить().Выбрать();
        Если Выборка.Следующий() Тогда
            // Напрямую меняем данные строки внутри контекста формы
            СтрокаТЧ.ЦенаЗаЕдиницу = Выборка.ЦенаЗаЕдиницу;
            СтрокаТЧ.ОстатокНаСкладе = Выборка.ОстатокНаСкладе;
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

В этом примере метод ПолучитьИдентификатор() возвращает уникальный числовой индекс строки таблицы формы. На сервере встроенный метод коллекции НайтиПоИдентификатору() позволяет мгновенно позиционироваться на нужной строке коллекции Объект.ПМ и внести изменения, которые автоматически улетят обратно на клиент.

Важный нюанс: оптимизация текста запроса

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


ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Цены.СрезПоследних(&Дата, ) КАК ЦеныСрезПоследних
ПО (ИСТИНА)
ГДЕ ОстаткиМатериаловОстатки.Материал = ЦеныСрезПоследних.Номенклатура

Никогда не используйте конструкцию ПО (ИСТИНА) совместно с условиями в секции ГДЕ для виртуальных таблиц!

Проанализируем эту ситуацию: при такой записи СУБД сначала выполнит полное Декартово произведение (соединит каждую строку остатков со всеми строками цен без ограничений), создав гигантскую временную таблицу в оперативной памяти (или TempDB), и только после этого применит фильтр ГДЕ. На рабочих базах данных с тысячами товаров это приведет к катастрофическому падению производительности и зависанию системы.

Правильный подход — всегда передавать отбор по конкретной номенклатуре непосредственно в параметры виртуальных таблиц: СрезПоследних(&Дата, Номенклатура = &Номенклатура) и Остатки(&Дата, Материал = &Номенклатура). Это заставит СУБД выбрать только одну нужную запись, обеспечивая мгновенный отклик интерфейса при изменении строки.

Используйте предложенные паттерны проектирования, разделяйте логику интерфейса и серверных расчетов, и ваши управляемые формы будут работать быстро и без досадных ошибок отображения типов.

← На главную