При формировании печатных форм в системе 1С:Предприятие разработчики часто сталкиваются с необходимостью кастомного вывода номеров страниц. Стандартный механизм колонтитулов, предоставляемый объектом КолонтитулТабличногоДокумента, имеет существенный недостаток: он отображается только при предварительном просмотре или непосредственной печати документа. Если же печатная форма выводится на экран для предварительного редактирования или анализа пользователем, стандартные колонтитулы остаются невидимыми.
В этой ситуации единственным выходом становится ручной вывод номеров страниц непосредственно в ячейки табличного документа через параметры области макета (например, в области подвала листа). Однако здесь возникает логическая проблема: в момент вывода подвала первой страницы мы уже знаем ее текущий номер, но еще не можем знать общее (итоговое) количество страниц в документе, так как формирование последующих страниц продолжается. Давайте подробно разберем, как решить эту задачу на платформе 1С:Предприятие 8.
Для реализации сквозной нумерации формата «Страница X из Y» без использования стандартных колонтитулов на практике применяют два основных архитектурных подхода:
Каждый из этих методов имеет свои преимущества и технологические нюансы. Разберем их по шагам.
Этот метод является наиболее производительным, так как заполнение макета происходит за один проход. Основная идея заключается в том, что метод Вывести() объекта ТабличныйДокумент возвращает объект типа ОбластьЯчеекТабличногоДокумента, которая указывает на конкретное место в уже сформированном результирующем документе. Нам не нужно запоминать текстовые адреса ячеек в формате «R1C1» — достаточно сохранить ссылки на эти возвращенные области.
Выясним причину, почему так важно сохранять именно объекты областей. Адресация ячеек может сдвигаться в процессе формирования документа, а прямая ссылка на область всегда гарантирует точный доступ к нужным ячейкам.
Посмотрим на пример реализации данного алгоритма:
ТабДок = Новый ТабличныйДокумент;
Макет = ПолучитьМакет("МакетПечати");
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
ОбластьПодвалЛиста = Макет.ПолучитьОбласть("ПодвалЛиста");
СписокПодвалов = Новый Массив;
ТекущийНомерСтраницы = 1;
// Выводим шапку документа
ТабДок.Вывести(ОбластьШапка);
// Цикл по данным для вывода строк
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
// Проверяем, уместится ли очередная строка вместе с подвалом листа
МассивПроверяемыхОбластей = Новый Массив;
МассивПроверяемыхОбластей.Добавить(ОбластьСтрока);
МассивПроверяемыхОбластей.Добавить(ОбластьПодвалЛиста);
Если Не ТабДок.ПроверитьВывод(МассивПроверяемыхОбластей) Тогда
// Если не влезает, выводим подвал для текущего листа
ОбластьПодвалЛиста.Параметры.НомерСтраницы = ТекущийНомерСтраницы;
// Параметр общего числа страниц пока оставляем пустым
ОбластьПодвалЛиста.Параметры.ВсегоСтраниц = "...";
ВыведенныйПодвал = ТабДок.Вывести(ОбластьПодвалЛиста);
// Сохраняем ссылку на область и номер страницы для последующего заполнения
СтруктураПодвала = Новый Структура;
СтруктураПодвала.Вставить("Область", ВыведенныйПодвал);
СтруктураПодвала.Вставить("НомерСтраницы", ТекущийНомерСтраницы);
СписокПодвалов.Добавить(СтруктураПодвала);
// Переходим на новый лист
ТабДок.ВывестиГоризонтальныйРазделительСтраниц();
ТекущийНомерСтраницы = ТекущийНомерСтраницы + 1;
// Повторно выводим шапку на новом листе, если это необходимо по ГОСТу
ТабДок.Вывести(ОбластьШапка);
КонецЕсли;
// Заполняем и выводим строку данных
ЗаполнитьЗначенияСвойств(ОбластьСтрока.Параметры, СтрокаДанных);
ТабДок.Вывести(ОбластьСтрока);
КонецЦикла;
// Выводим финальный подвал последнего листа
ОбластьПодвалЛиста.Параметры.НомерСтраницы = ТекущийНомерСтраницы;
ОбластьПодвалЛиста.Параметры.ВсегоСтраниц = "...";
ВыведенныйПодвал = ТабДок.Вывести(ОбластьПодвалЛиста);
СтруктураПодвала = Новый Структура;
СтруктураПодвала.Вставить("Область", ВыведенныйПодвал);
СтруктураПодвала.Вставить("НомерСтраницы", ТекущийНомерСтраницы);
СписокПодвалов.Добавить(СтруктураПодвала);
Метод ПроверитьВывод() заслуживает отдельного подробного рассмотрения. Он проверяет, поместится ли переданный массив областей на текущую страницу с учетом уже выведенных строк, настроек полей, колонтитулов и масштаба. Важно передавать в него не только следующую строку данных, но и подвал листа. Ведь если сама строка поместится, но для подвала места уже не останется, документ перенесет подвал на следующий лист отдельно от данных, что визуально недопустимо.
После завершения основного цикла формирования документа нам необходимо определить общее число страниц и заполнить отложенные параметры. Для получения итогового количества страниц воспользуемся методом КоличествоСтраниц().
Проанализируем важную технологическую особенность платформы 1С. Метод КоличествоСтраниц() напрямую обращается к драйверу принтера по умолчанию, установленного в операционной системе, на которой выполняется код (на клиенте или на сервере). Это необходимо платформе для точного расчета геометрии страниц с учетом непечатных зон, полей и масштаба.
Если на сервере или клиентской машине не установлен ни один принтер, если сетевой принтер недоступен или драйвер принтера по умолчанию работает некорректно, метод КоличествоСтраниц() вызовет критическое исключение работы программы. Поэтому вызов этого метода в обязательном порядке необходимо оборачивать в блок Попытка...Исключение:
ВсегоСтраниц = 0;
Попытка
ВсегоСтраниц = ТабДок.КоличествоСтраниц();
Исключение
// Записываем ошибку в журнал регистрации для администратора
ЗаписьЖурналаРегистрации("ПечатьДокумента", УровеньЖурналаРегистрации.Предупреждение, , ,
"Не удалось определить количество страниц: " + ОписаниеОшибки());
КонецПопытки;
// Если количество страниц успешно определено, заполняем сохраненные области
Если ВсегоСтраниц > 0 Тогда
Для Каждого ТекущийПодвал Из СписокПодвалов Цикл
// Обращаемся к конкретной ячейке выведенного подвала.
// Например, параметр "ВсегоСтраниц" находится в первой строке и третьей колонке выведенной области
ТекущийПодвал.Область.Область(1, 3, 1, 3).Текст = Строка(ВсегоСтраниц);
КонецЦикла;
КонецЕсли;
При использовании метода постобработки областей разработчики часто сталкиваются с неприятным визуальным багом, который называют «эффектом домино». Давайте разберем причину его возникновения.
Изначально при формировании документа в ячейку «Всего страниц» мы выводим временный короткий шаблон (например, троеточие «...» или просто пробел). Когда в самом конце мы заменяем этот шаблон на реальное число (например, «128»), длина текста в ячейке существенно увеличивается. Если для данной ячейки или строки в макете включено свойство АвтоВысотаСтроки, платформа может автоматически увеличить высоту строки подвала, чтобы уместить длинный текст. Увеличение высоты строки хотя бы на несколько пикселей на каждой странице неизбежно сдвинет строки документа вниз. В результате верстка документа смещается, и общее количество страниц внезапно увеличивается (например, было 10 страниц, а после заполнения параметров их стало 11). Это приводит к тому, что напечатанный номер «из 10» на последней странице окажется неверным, так как фактически появилась 11-я страница.
Решение для предотвращения сдвига страниц:
АвтоВысотаСтроки для строки подвала, задав ей фиксированную высоту.РазмещениеТекста в значение Забивать или Обрезать, чтобы превышение длины строки не приводило к автоматическому изменению геометрии табличного документа.Рассмотрим альтернативный метод формирования, который полностью исключает любые риски искажения верстки из-за динамического изменения значений параметров в ячейках. Суть метода заключается в предварительном формировании печатной формы в «черновой» документ для точного расчета страниц.
Разберем по шагам реализацию этого подхода:
// Шаг 1: Создаем черновой табличный документ
ЧерновойТабДок = Новый ТабличныйДокумент;
// Запускаем первый проход формирования (без заполнения общего количества страниц)
СформироватьТабличныйДокумент(ЧерновойТабДок, Истина);
// Шаг 2: Программно определяем точное количество страниц
ВсегоСтраниц = 0;
Попытка
ВсегоСтраниц = ЧерновойТабДок.КоличествоСтраниц();
Исключение
ВсегоСтраниц = 1; // Резервное значение по умолчанию
КонецПопытки;
// Шаг 3: Создаем чистовой табличный документ и запускаем второй проход формирования
ЧистовойТабДок = Новый ТабличныйДокумент;
СформироватьТабличныйДокумент(ЧистовойТабДок, Ложь, ВсегоСтраниц);
// Выводим чистовой документ пользователю
ЧистовойТабДок.Показать();
В данном случае процедура СформироватьТабличныйДокумент инкапсулирует в себе всю логику обхода выборки данных и вывода областей. Посмотрим на пример структуры такой процедуры:
Процедура СформироватьТабличныйДокумент(ТабДок, ЭтоЧерновойПроход, ВсегоСтраниц = 0)
Макет = ПолучитьМакет("МакетПечати");
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
ОбластьПодвалЛиста = Макет.ПолучитьОбласть("ПодвалЛиста");
ТекущийНомерСтраницы = 1;
ТабДок.Вывести(ОбластьШапка);
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
МассивПроверяемыхОбластей = Новый Массив;
МассивПроверяемыхОбластей.Добавить(ОбластьСтрока);
МассивПроверяемыхОбластей.Добавить(ОбластьПодвалЛиста);
Если Не ТабДок.ПроверитьВывод(МассивПроверяемыхОбластей) Тогда
// Заполняем параметры подвала листа
ОбластьПодвалЛиста.Параметры.НомерСтраницы = ТекущийНомерСтраницы;
Если ЭтоЧерновойПроход Тогда
ОбластьПодвалЛиста.Параметры.ВсегоСтраниц = "999"; // Временный заполнитель максимальной ширины
Иначе
ОбластьПодвалЛиста.Параметры.ВсегоСтраниц = Строка(ВсегоСтраниц);
КонецЕсли;
ТабДок.Вывести(ОбластьПодвалЛиста);
ТабДок.ВывестиГоризонтальныйРазделительСтраниц();
ТекущийНомерСтраницы = ТекущийНомерСтраницы + 1;
ТабДок.Вывести(ОбластьШапка);
КонецЕсли;
ЗаполнитьЗначенияСвойств(ОбластьСтрока.Параметры, СтрокаДанных);
ТабДок.Вывести(ОбластьСтрока);
КонецЦикла;
// Выводим подвал последнего листа
ОбластьПодвалЛиста.Параметры.НомерСтраницы = ТекущийНомерСтраницы;
Если ЭтоЧерновойПроход Тогда
ОбластьПодвалЛиста.Параметры.ВсегоСтраниц = "999";
Иначе
ОбластьПодвалЛиста.Параметры.ВсегоСтраниц = Строка(ВсегоСтраниц);
КонецЕсли;
ТабДок.Вывести(ОбластьПодвалЛиста);
КонецПроцедуры
Обратите внимание на важную деталь: на черновом проходе мы передаем в параметр «Всего страниц» строку максимальной длины (например, «999»). Это гарантирует, что при первом проходе макет сразу рассчитает высоту строк с учетом максимально возможной ширины текста номеров страниц. При втором проходе реальное число страниц (например, «12») гарантированно уместится в эти же габариты ячейки, и верстка ни в коем случае не изменится.
Выбор оптимального метода зависит от сложности структуры вашей печатной формы и объема выводимых данных:
Таким образом, мы рассмотрели надежные способы реализации сквозной нумерации страниц в 1С при использовании ручной разбивки на листы через области макета. Применяя данные рекомендации и не забывая про обработку исключительных ситуаций работы с принтером, вы сможете создавать профессиональные и стабильные печатные формы.