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