Как исправить ошибку «Значение не является значением объектного типа (ПолучитьОбласть)» в 1С?

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

При разработке отчетов и обработок в среде 1С:Предприятие 8.3 на управляемых формах программисты часто сталкиваются со стандартной, но крайне неприятной ошибкой: «Значение не является значением объектного типа (ПолучитьОбласть)». Эта ошибка полностью блокирует формирование отчета и выводит пользователя в замешательство.

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

Выясняем причину возникновения исходной ошибки

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


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

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

Почему же так происходит? Дело в архитектуре управляемого приложения. На управляемой форме реквизит Отчет (или Объект для обработок и документов) имеет тип ДанныеФормыСтруктура, а не привычный ОтчетОбъект.

Тип данных ДанныеФормыСтруктура представляет собой облегченный транспортный контейнер, предназначенный исключительно для быстрой передачи данных между клиентом и сервером. Этот объект «не знает» о метаданных конфигурации, у него нет встроенных макетов, и у него физически отсутствует метод ПолучитьМакет().

Решение проблемы по шагам

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

Способ 1. Использование функции РеквизитФормыВЗначение (Решение автора)

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

Давайте посмотрим, как изменится код получения макета:


НовТабДок = Новый ТабличныйДокумент;

// Конвертируем данные формы в полноценный объект отчета
ОтчетОбъект = РеквизитФормыВЗначение("Отчет");

// Теперь нам доступен метод ПолучитьМакет()
Макет = ОтчетОбъект.ПолучитьМакет("Макет");

Шапка = Макет.ПолучитьОбласть("Шапка");
НовТабДок.Вывести(Шапка);

Важные нюансы данного подхода:

  1. Высокая ресурсоемкость: Метод РеквизитФормыВЗначение() заставляет сервер полностью инициализировать объект со всеми его внутренними переменными, модулями и обработчиками событий. Это относительно «тяжелая» операция.
  2. Контекстная зависимость: Эту функцию можно вызывать только внутри контекстных серверных процедур (помеченных директивой &НаСервере). В быстрых неконтекстных вызовах &НаСервереБезКонтекста данный метод использовать нельзя.

Способ 2. Прямое обращение через менеджер (Самый быстрый способ)

Если наш отчет встроен в конфигурацию (то есть не является внешним файлом .erf), нам совершенно не обязательно создавать тяжелый объект отчета через форму. Мы можем обратиться к его менеджеру напрямую через метаданные конфигурации. Этот способ работает мгновенно и не требует контекста формы:


// Получаем макет напрямую из метаданных конфигурации
Макет = Отчеты.ИмяВашегоОтчета.ПолучитьМакет("Макет");

Этот вариант является наиболее предпочтительным для встроенных в систему отчетов, так как он существенно экономит ресурсы сервера и работает быстрее.

Способ 3. Перенос логики в модуль объекта (Для внешних отчетов)

Если мы разрабатываем внешний отчет или обработку, хорошей практикой считается разделение интерфейсной логики и логики формирования данных. Вынесем процедуру формирования табличного документа в Модуль объекта нашего внешнего отчета, сделав её экспортной:


// Код в Модуле объекта внешнего отчета:
Процедура СформироватьОтчетНаСервере(ТабДок, Дата1, Дата2) Экспорт
    
    // В модуле объекта макет доступен напрямую без дополнительных функций!
    Макет = ПолучитьМакет("Макет");
    
    // ... (код заполнения табличного документа)
    
КонецПроцедуры

В модуле формы нам останется лишь вызвать эту экспортную процедуру:


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

Этот подход делает код чистым, модульным и существенно упрощает дальнейшую поддержку и обновление отчета.

Разбор скрытой критической ошибки в коде автора

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


Для Каждого СтрокаТЗ Из ТЗ Цикл
    СтрокаТЧ = Макет.ПолучитьОбласть("СтрокаТЧ");
    СтрокаТЧ.Параметры.День = ТЗ.День; // <-- КРИТИЧЕСКАЯ ОШИБКА!
    НовТабДок.Вывести(СтрокаТЧ);
КонецЦикла;

В чем заключается проблема?
В строке заполнения параметров происходит обращение к ТЗ.День. Переменная ТЗ — это вся Таблица Значений целиком. У самого объекта таблицы значений нет свойства День (это имя колонки, а не свойство объекта). При выполнении этого участка кода система незамедлительно выдаст ошибку: «Поле объекта не обнаружено (День)».

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


СтрокаТЧ.Параметры.День = СтрокаТЗ.День; // Правильное обращение к текущей строке

Оптимизация производительности: ускоряем вывод макета

Давайте обратим внимание на то, где именно получается область строки в коде автора:


Для Каждого СтрокаТЗ Из ТЗ Цикл
    СтрокаТЧ = Макет.ПолучитьОбласть("СтрокаТЧ"); // Получение области внутри цикла!
    ...
КонецЦикла;

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

Рекомендуемый метод оптимизации:
Получим область строки макета ровно один раз до начала цикла, а внутри цикла будем лишь перезаписывать параметры и выводить область в итоговый табличный документ. При выводе область автоматически копируется со всеми текущими параметрами.


// Оптимизация: получаем область один раз до начала цикла
СтрокаТЧ = Макет.ПолучитьОбласть("СтрокаТЧ");

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

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

Итоговый правильный и оптимизированный вариант кода

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


&НаСервере
Процедура ПросмотретьНаСервере()
    
    // Инициализируем таблицу значений
    ТЗ = Новый ТаблицаЗначений;
    ТЗ.Колонки.Добавить("День");
    ТЗ.Колонки.Добавить("Месяц");
    
    ТекДат = НачалоДня(Отчет.Дата1);
    
    // Заполняем таблицу значений датами периода
    Пока ТекДат <= НачалоДня(Отчет.Дата2) Цикл
        НовСтр = ТЗ.Добавить();
        НовСтр.День = ТекДат;
        НовСтр.Месяц = Месяц(ТекДат);
        
        ТекДат = ТекДат + 86400; // Добавляем 1 день (в секундах)
    КонецЦикла;
    
    НовТабДок = Новый ТабличныйДокумент;    
    
    // Получаем макет оптимальным способом (для встроенного отчета)
    // Если отчет внешний - используем РеквизитФормыВЗначение("Отчет").ПолучитьМакет("Макет")
    Макет = Отчеты.ИмяВашегоОтчета.ПолучитьМакет("Макет");
    
    // Выводим шапку отчета
    Шапка = Макет.ПолучитьОбласть("Шапка"); 
    НовТабДок.Вывести(Шапка);
    
    // Оптимизация: Получаем область строки ДО цикла
    СтрокаТЧ = Макет.ПолучитьОбласть("СтрокаТЧ");
    
    // Цикл вывода данных
    Для Каждого СтрокаТЗ Из ТЗ Цикл
        // Исправлено: обращаемся к СтрокаТЗ, а не к ТЗ
        СтрокаТЧ.Параметры.День = СтрокаТЗ.День; 
        НовТабДок.Вывести(СтрокаТЧ);
    КонецЦикла;
    
    // Здесь можно вывести готовый ТабличныйДокумент на форму пользователю
    
КонецПроцедуры

Мы успешно разобрались в причинах возникновения ошибки, научились обходить ограничения объектной модели управляемых форм, исправили критический баг обращения к колонкам таблицы значений и применили стандарты оптимизации 1С для работы с выводом областей макета.

← На главную