При работе в среде обычных форм (платформа 1С:Предприятие 8.1, 8.2 и режим обычных форм в 8.3) разработчики часто сталкиваются с задачей выгрузки содержимого табличного поля в файл формата ТабличныйДокумент (MXL). Нам хорошо известна стандартная интерактивная команда контекстного меню "Вывести список...". Однако при попытке повторить это действие программно возникает ряд сложностей. Стандартный вывод учитывает видимость колонок, их ширину, пользовательские настройки цветов, шрифты шапки и строк, а также сложное взаимное расположение колонок (так называемые "этажерки" или многострочные колонки).
В этой статье мы подробно разберем, как воссоздать этот механизм программно. Мы проанализируем несколько способов реализации: от самого простого и быстрого (для классических однострочных таблиц) до универсального алгоритма, способного корректно обрабатывать многострочные шапки и сложные блоки колонок.
---
Самый простой способ быстро получить печатную форму из таблицы значений — это использование объекта ПостроительОтчета. Давайте посмотрим на классический пример такого кода:
Построитель = Новый ПостроительОтчета;
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТаблицаЗначений);
ТабДокумент = Новый ТабличныйДокумент;
Построитель.Вывести(ТабДокумент);
ТабДокумент.Показать();
Этот метод работает чрезвычайно быстро, но у него есть существенные недостатки:
Видимость равное Ложь).Поэтому для точной имитации поведения команды "Вывести список" нам придется реализовать собственный алгоритм обхода элементов формы.
---
Рассмотрим ситуацию, когда наша таблица на форме имеет простую структуру: все колонки расположены в один ряд, нет сложных объединений. В этом случае мы можем написать очень компактную функцию, которая обходит видимые колонки и строки, извлекая оформление непосредственно из свойства ОформлениеСтроки табличного поля.
Давайте пошагово разберем эту функцию:
Функция ВывестиСписокЛаконичный(ТабличноеПолеФормы) Экспорт
мТабДок = Новый ТабличныйДокумент;
Линия = Новый Линия(ТипЛинииЯчейкиТабличногоДокумента.Сплошная, 1);
мСтрЯч = 1;
мКолЯч = 0;
// Шаг 1. Формируем шапку таблицы
Для Каждого мКол Из ТабличноеПолеФормы.Колонки Цикл
Если мКол.Видимость Тогда
мКолЯч = мКолЯч + 1;
мОб = мТабДок.Область(мСтрЯч, мКолЯч);
мОб.Текст = мКол.ТекстШапки;
// Задаем стандартные цвета для шапки
мОб.ЦветТекста = WindowsЦвета.ТекстКнопки;
мОб.ЦветФона = WindowsЦвета.Кнопка;
мОб.Обвести(Линия, Линия, Линия, Линия);
мОб.ШиринаКолонки = мКол.Ширина;
КонецЕсли;
КонецЦикла;
// Шаг 2. Обходим строки и переносим оформление
Для Каждого мСтр Из ТабличноеПолеФормы.Значение Цикл
мСтрЯч = мСтрЯч + 1;
// Получаем оформление текущей строки из элемента формы
мОф = ТабличноеПолеФормы.ОформлениеСтроки(мСтр);
мКолЯч = 0;
Для Каждого мКол Из ТабличноеПолеФормы.Колонки Цикл
Если мКол.Видимость Тогда
мКолЯч = мКолЯч + 1;
// Находим оформление конкретной ячейки
мЯч = мОф.Ячейки.Найти(мКол.Имя);
мОб = мТабДок.Область(мСтрЯч, мКолЯч);
// Переносим текст и оформление (цвета, шрифты)
мОб.Текст = мЯч.Текст;
мОб.ЦветТекста = мЯч.ЦветТекста;
мОб.ЦветФона = мЯч.ЦветФона;
мОб.Шрифт = мЯч.Шрифт;
мОб.Обвести(Линия, Линия, Линия, Линия);
КонецЕсли;
КонецЦикла;
КонецЦикла;
Возврат мТабДок;
КонецФункции
Преимущества данного метода:
Недостатки: метод не поддерживает иерархическое расположение колонок (свойства ПоложениеКолонки) и аварийно завершится или выдаст некорректную сетку, если на форме настроены многоэтажные шапки.
---
Если в вашей конфигурации используются сложные таблицы, где колонки сгруппированы в блоки или расположены одна под другой, нам потребуется более развитый алгоритм. Выясним причину сложности: для таких таблиц позиция ячейки в результирующем ТабличномДокументе не может быть определена простым инкрементом индекса колонки. Нам нужно предварительно рассчитать геометрическую сетку расположения ячеек на основе свойств Положение, Ширина и Высота колонок табличного поля.
Проанализируем универсальный код, который решает эту задачу. Алгоритм состоит из расчета блоков ячеек и последующего их позиционирования.
// Главная функция вывода списка
Функция ВывестиСложныйСписок(ТЗНаФорме) Экспорт
ТабДокумент = Новый ТабличныйДокумент;
// 1. Фильтруем только видимые колонки
масКолонки = Новый Массив;
Для Каждого Колонка Из ТЗНаФорме.Колонки Цикл
Если Колонка.Видимость Тогда
масКолонки.Добавить(Колонка);
КонецЕсли;
КонецЦикла;
ШагНомерСтроки = 0;
МаксШирина = 0;
// Вычисляем геометрию сетки блоков
тзБлоков = ПолучитьТЗБлоков(масКолонки, ШагНомерСтроки, МаксШирина);
// 2. Получаем параметры оформления шапки
сткПараметрыШапки = ПолучитьПараметрыШапки(ТЗНаФорме);
ЛинияШапки = сткПараметрыШапки.ЛинияШапки;
НомерСтроки = 1;
// Выводим шапку
Если сткПараметрыШапки.ВыводитьШапку Тогда
Для Каждого СтрокаБлока Из тзБлоков Цикл
Колонка = СтрокаБлока.Колонка;
// Вычисляем координаты ячейки в сетке табличного документа
СтартПоГоризонтали = 1 + (СтрокаБлока.НомерБлока - 1) * МаксШирина + (СтрокаБлока.НомерКолонки - 1) * СтрокаБлока.ШиринаКолонки;
СтартПоВертикали = НомерСтроки + (СтрокаБлока.НомерСтроки - 1);
ФинишПоГоризонтали = СтартПоГоризонтали + СтрокаБлока.ШиринаКолонки - 1;
ФинишПоВертикали = СтартПоВертикали + СтрокаБлока.ВысотаСтроки - 1;
Координата = "R" + СтартПоВертикали + "C" + СтартПоГоризонтали + ":R" + ФинишПоВертикали + "C" + ФинишПоГоризонтали;
Ячейка = ТабДокумент.Область(Координата);
Ячейка.Объединить();
// Настройка стилей шапки
Ячейка.ЦветТекста = сткПараметрыШапки.ЦветТекстаШапки;
Ячейка.ЦветФона = сткПараметрыШапки.ЦветФонаШапки;
Ячейка.Шрифт = сткПараметрыШапки.ШрифтШапки;
Ячейка.Обвести(ЛинияШапки, ЛинияШапки, ЛинияШапки, ЛинияШапки);
Ячейка.ВертикальноеПоложение = ВертикальноеПоложение.Центр;
Ячейка.ГоризонтальноеПоложение = Колонка.ГоризонтальноеПоложениеВШапке;
Ячейка.ШиринаКолонки = Колонка.Ширина;
Ячейка.Текст = Колонка.ТекстШапки;
КонецЦикла;
НомерСтроки = НомерСтроки + ШагНомерСтроки;
КонецЕсли;
// 3. Вывод строк данных
ЛинияГор = Неопределено;
ЛинияВер = Неопределено;
ПолучитьЛинииТаблицы(ТЗНаФорме, ЛинияГор, ЛинияВер);
Для Каждого СтрокаДанных Из ТЗНаФорме.Значение Цикл
ОформлениеСтроки = ТЗНаФорме.ОформлениеСтроки(СтрокаДанных);
Для Каждого СтрокаБлока Из тзБлоков Цикл
Колонка = СтрокаБлока.Колонка;
// Вычисляем координаты ячейки данных
СтартПоГоризонтали = 1 + (СтрокаБлока.НомерБлока - 1) * МаксШирина + (СтрокаБлока.НомерКолонки - 1) * СтрокаБлока.ШиринаКолонки;
СтартПоВертикали = НомерСтроки + (СтрокаБлока.НомерСтроки - 1);
ФинишПоГоризонтали = СтартПоГоризонтали + СтрокаБлока.ШиринаКолонки - 1;
ФинишПоВертикали = СтартПоВертикали + СтрокаБлока.ВысотаСтроки - 1;
Координата = "R" + СтартПоВертикали + "C" + СтартПоГоризонтали + ":R" + ФинишПоВертикали + "C" + ФинишПоГоризонтали;
Ячейка = ТабДокумент.Область(Координата);
Ячейка.Объединить();
ОформлениеЯчейки = ОформлениеСтроки.Ячейки[Колонка.Имя];
// Настройка динамических цветов с учетом автоцветов платформы
Ячейка.ЦветТекста = ?(ОформлениеЯчейки.ЦветТекста.Вид = ВидЦвета.АвтоЦвет, ЦветаСтиля.ЦветТекстаФормы, ОформлениеЯчейки.ЦветТекста);
Ячейка.ЦветФона = ?(ОформлениеЯчейки.ЦветФона.Вид = ВидЦвета.АвтоЦвет, ЦветаСтиля.ЦветФонаПоля, ОформлениеЯчейки.ЦветФона);
Ячейка.Шрифт = ?(ОформлениеЯчейки.Шрифт.Вид = ВидШрифта.АвтоШрифт, Неопределено, ОформлениеЯчейки.Шрифт);
Ячейка.Обвести(ЛинияВер, ЛинияГор, ЛинияВер, ЛинияГор);
Ячейка.ВертикальноеПоложение = ВертикальноеПоложение.Центр;
Ячейка.ГоризонтальноеПоложение = Колонка.ГоризонтальноеПоложениеВКолонке;
Ячейка.Текст = ОформлениеЯчейки.Текст;
КонецЦикла;
НомерСтроки = НомерСтроки + ШагНомерСтроки;
КонецЦикла;
Возврат ТабДокумент;
КонецФункции
Для корректного функционирования этого алгоритма нам потребуются вспомогательные функции, которые анализируют расположение элементов на форме.
Рассмотрим по шагам логику расчета блоков колонок. Функция ПолучитьТЗБлоков анализирует значение свойства Положение (которое принимает значения системного перечисления ПоложениеКолонки) и строит таблицу координат:
Функция ПолучитьТЗБлоков(масКолонок, МаксНомерСтроки, МаксШирина)
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Колонка");
ТЗ.Колонки.Добавить("НомерБлока", Новый ОписаниеТипов("Число"));
ТЗ.Колонки.Добавить("НомерСтроки", Новый ОписаниеТипов("Число"));
ТЗ.Колонки.Добавить("НомерКолонки", Новый ОписаниеТипов("Число"));
ТЗ.Колонки.Добавить("ВысотаСтроки", Новый ОписаниеТипов("Число"));
ТЗ.Колонки.Добавить("ШиринаКолонки", Новый ОписаниеТипов("Число"));
НомерБлока = 0;
НомерСтроки = 0;
НомерКолонки = 0;
Для Каждого Колонка Из масКолонок Цикл
Если Колонка.Положение = ПоложениеКолонки.НоваяКолонка Тогда
НомерБлока = НомерБлока + 1;
НомерСтроки = 1;
НомерКолонки = 1;
ИначеЕсли Колонка.Положение = ПоложениеКолонки.НаСледующейСтроке Тогда
НомерСтроки = НомерСтроки + 1;
НомерКолонки = 1;
ИначеЕсли Колонка.Положение = ПоложениеКолонки.ВТойЖеКолонке Тогда
НомерКолонки = НомерКолонки + 1;
КонецЕсли;
НС = ТЗ.Добавить();
НС.Колонка = Колонка;
НС.НомерБлока = НомерБлока;
НС.НомерСтроки = НомерСтроки;
НС.НомерКолонки = НомерКолонки;
КонецЦикла;
// Расчет высоты и ширины ячеек
МаксНомерСтроки = 0;
Для Каждого Стр Из ТЗ Цикл
МаксНомерСтроки = Макс(МаксНомерСтроки, Стр.НомерСтроки);
КонецЦикла;
// Заполняем высоты строк
Для Каждого Стр Из ТЗ Цикл
Стр.ВысотаСтроки = 1; // Базовая высота ячейки
КонецЦикла;
// Рассчитываем ширину в зависимости от максимального количества колонок в блоке
МаксШирина = 1;
Для НомерБлокаИтератор = 1 По НомерБлока Цикл
СтрокиБлока = ТЗ.НайтиСтроки(Новый Структура("НомерБлока", НомерБлокаИтератор));
МаксКолонокВБлоке = 1;
Для Каждого СтрБлока Из СтрокиБлока Цикл
МаксКолонокВБлоке = Макс(МаксКолонокВБлоке, СтрБлока.НомерКолонки);
КонецЦикла;
Для Каждого СтрБлока Из СтрокиБлока Цикл
СтрБлока.ШиринаКолонки = 1; // Пропорциональное распределение
КонецЦикла;
МаксШирина = Макс(МаксШирина, МаксКолонокВБлоке);
КонецЦикла;
Возврат ТЗ;
КонецФункции
Также добавим вспомогательные функции для корректного сбора настроек оформления таблицы (наличие сетки, рамки, стили шрифтов):
Процедура ПолучитьЛинииТаблицы(ТабличноеПоле, ЛинияГор, ЛинияВер)
// Определение типа рамки элемента управления
Если ТабличноеПоле.Рамка.Вид = ВидРамки.ЭлементСтиля Тогда
Толщина = РамкиСтиля.РамкаЭлементаУправления.Толщина;
ТипЛинии = РамкиСтиля.РамкаЭлементаУправления.ТипРамки;
Иначе
Толщина = ТабличноеПоле.Рамка.Толщина;
ТипЛинии = ТабличноеПоле.Рамка.ТипРамки;
КонецЕсли;
Если ТипЛинии = Неопределено Тогда
ТипЛинии = ТипЛинииЯчейкиТабличногоДокумента.Сплошная;
Толщина = 1;
КонецЕсли;
// Проверяем видимость сетки
ЛинияГор = ?(ТабличноеПоле.ГоризонтальныеЛинии, Новый Линия(ТипЛинии, Толщина), Новый Линия(ТипЛинииЯчейкиТабличногоДокумента.НетЛинии, -1));
ЛинияВер = ?(ТабличноеПоле.ВертикальныеЛинии, Новый Линия(ТипЛинии, Толщина), Новый Линия(ТипЛинииЯчейкиТабличногоДокумента.НетЛинии, -1));
КонецПроцедуры
Функция ПолучитьПараметрыШапки(ТабличноеПоле)
сткПараметры = Новый Структура("ВыводитьШапку, ЦветТекстаШапки, ЦветФонаШапки, ВысотаШапки, ШрифтШапки, ЛинияШапки");
сткПараметры.ВыводитьШапку = ТабличноеПоле.Шапка;
Если сткПараметры.ВыводитьШапку Тогда
сткПараметры.ЦветТекстаШапки = ?(Строка(ТабличноеПоле.ЦветТекстаШапки) = "Авто", ЦветаСтиля.ЦветТекстаФормы, ТабличноеПоле.ЦветТекстаШапки);
сткПараметры.ЦветФонаШапки = ?(Строка(ТабличноеПоле.ЦветФонаШапки) = "Авто", ЦветаСтиля.ЦветФонаКнопки, ТабличноеПоле.ЦветФонаШапки);
сткПараметры.ВысотаШапки = ТабличноеПоле.ВысотаШапки;
сткПараметры.ШрифтШапки = ТабличноеПоле.ШрифтШапки;
сткПараметры.ЛинияШапки = Новый Линия(ТипЛинииРисункаТабличногоДокумента.Сплошная, 1);
КонецЕсли;
Возврат сткПараметры;
КонецФункции
---
Мы проанализировали внутренние механизмы платформы 1С:Предприятие по выводу списков в формат MXL. Выяснилось, что:
Используя предложенные модули, вы сможете легко автоматизировать выгрузку реестров, подготовку отчетов на основе табличных частей документов и отправку их по электронной почте в виде вложений формата *.mxl или *.xls.