Почему при заполнении параметров макета 1С все элементы массива областей становятся одинаковыми?

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

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

Рассмотрим исходный фрагмент кода, вызвавший затруднение:


Док = Документы.ПоступлениеТоваровУслуг.Выбрать(НачалоДня(ТекДата), КонецДня(ТекДата));
МассивВыводимыхОбластей = Новый Массив;

Пока Док.Следующий() Цикл
    ОбластьДок = Макет.ПолучитьОбласть("Док");
    ОбластьДок.Параметры.Отдел = Док;
    МассивВыводимыхОбластей.Добавить(ОбластьДок);
КонецЦикла;

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

Причина 1: Передача объекта выборки вместо конкретного значения

Переменная Док, полученная вызовом метода Выбрать(), содержит объект типа ДокументВыборка (аналогично ведет себя и ВыборкаИзРезультатаЗапроса). Этот объект является курсором (указателем на текущую запись в наборе данных), а не самим документом или ссылкой на него.

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

Чтобы избежать такого поведения, необходимо передавать в параметры конечное значение, а не курсор выборки:

  1. Ссылку на объект: используем свойство Док.Ссылка вместо объекта выборки. Ссылка является неизменяемым идентификатором конкретного документа.
  2. Примитивные типы: если требуется вывести текстовое представление, явно приводим значение к строке через Строка(Док.Ссылка) или считываем конкретные реквизиты (например, Док.Номер, Док.Дата).

Причина 2: Архитектурная ошибка проверки вывода на страницу

Выясним причину, ради которой разработчик накапливал области в массиве. Как указано в задаче, массив формировался для последующего вызова метода ТабДок.ПроверитьВывод(МассивВыводимыхОбластей).

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

Кроме того, регулярный вызов Макет.ПолучитьОбласть("Док") внутри цикла порождает в памяти множество тяжеловесных объектов типа ТабличныйДокумент. Накопление тысяч таких объектов в массиве создает критическую нагрузку на оперативную память рабочего процесса.

Правильный паттерн формирования печатной формы

Разберем эталонный алгоритм, который решает проблему дублирования данных и корректно разбивает документ на страницы:

  1. Получаем область строки макета один раз до входа в цикл.
  2. Внутри цикла перезаполняем параметры области правильными типами данных (через Ссылка или методом Заполнить()).
  3. Помещаем в массив проверки только ту область (или неразрывный блок строк, например, строку и итоговый подвал), которую нужно проверить перед выводом.
  4. Если проверка возвращает Ложь, вставляем горизонтальный разделитель страниц.
  5. Сразу выводим проверенную область в итоговый табличный документ методом Вывести().

Посмотрим на программную реализацию:


РезТабДок = Новый ТабличныйДокумент;
ОбластьСтрока = Макет.ПолучитьОбласть("Док");

// Массив для проверки создаем один раз для снижения нагрузки на память
МассивДляПроверки = Новый Массив;

Док = Документы.ПоступлениеТоваровУслуг.Выбрать(НачалоДня(ТекДата), КонецДня(ТекДата));

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

Нюансы работы с методом ПроверитьВывод()

При использовании метода проверки страниц важно учитывать платформенные особенности:

  1. Зависимость от принтера: платформа рассчитывает высоту печатной области на основании настроек текущего драйвера принтера по умолчанию. Если формирование отчета выполняется на стороне сервера (например, в регламентном задании под управлением Linux или Windows без настроенного принтера по умолчанию), расчет может выполняться с погрешностями.
  2. Свойство АвтоМасштаб: если для табличного документа включено свойство АвтоМасштаб = Истина, использование метода ПроверитьВывод() теряет смысл, так как итоговый коэффициент сжатия страниц рассчитывается платформой только после полного завершения формирования документа.
  3. Автоматический перенос шапки: чтобы заголовок таблицы автоматически печатался на каждом новом листе, не требуется вручную проверять вывод шапки. Достаточно указать номера строк шапки в свойстве табличного документа ПовторятьПриПечатиСтроки.
← На главную