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