Как вывести многоуровневые группировки в одну колонку дерева значений в обычном табличном поле?

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

При разработке интерфейсов на обычных формах часто возникает задача отобразить данные со сложной иерархией в элементе управления ТабличноеПоле, связанном с объектом ДеревоЗначений. Типичный пример: необходимо отобразить цепочку вложенности «Склад → Зона → Номенклатура» вместе с количественными остатками.

Однако при стандартной выгрузке из СКД или при использовании метода запроса Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам) разработчики сталкиваются с неприятным поведением платформы: вместо компактного дерева с одной колонкой иерархии формируется «лесенка». Под каждую группировку создается отдельная колонка, и строки смещаются вправо, оставляя пустые ячейки на промежуточных уровнях. Разберем, почему так происходит и как получить аккуратное дерево значений с единой колонкой иерархии.

Выясним причину проблемы

Проанализируем механику работы платформы 1С. Когда мы настраиваем СКД с выводом в коллекцию значений или выполняем запрос с конструкцией ИТОГИ ПО, платформа помещает каждую группировку в собственное поле. При вызове стандартного метода Выгрузить() объект ДеревоЗначений получает столько колонок, сколько полей описано в выборке и группировках. В строке верхнего уровня (например, склад) заполнена только колонка Склад, а колонки Зона и Номенклатура остаются пустыми. На следующем уровне заполнена колонка Зона, а остальные пусты.

Для того чтобы получить компактный вид, где все разнородные узлы отображаются в одной первой колонке с плюсиками разворачивания, требуется либо переформировать само дерево значений, либо управлять отображением строк на уровне табличного поля формы.

Рассмотрим основные способы решения этой задачи.

Способ 1. Построение дерева с единой колонкой через запрос с итогами

Если исходные данные формируются запросом (или исходную таблицу значений можно передать во временную таблицу запроса), наиболее надежный путь — отказаться от слепой автовыгрузки и программно обойти результат по группировкам. В этом случае мы создаем ДеревоЗначений вручную всего с двумя колонками: универсальной колонкой иерархии (без ограничения типа) и числовой колонкой ресурса.

Разберем реализацию по шагам:

  1. Описываем в запросе расчет итогов с помощью секции ИТОГИ ПО.
  2. Создаем объект ДеревоЗначений с колонками Колонка и Остаток.
  3. Выполняем обход уровней выборки с помощью метода Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам) и записываем значение текущей группировки в единую колонку строки дерева.

Посмотрим на пример программного кода:


// Подготовим запрос с группировками
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТаблицаОстатков.Склад КАК Склад,
    |   ТаблицаОстатков.Зона КАК Зона,
    |   ТаблицаОстатков.Номенклатура КАК Номенклатура,
    |   ТаблицаОстатков.КоличествоОстаток КАК Остаток
    |ПОМЕСТИТЬ ВТ_Остатки
    |ИЗ
    |   &ИсходнаяТаблица КАК ТаблицаОстатков
    |;
    |
    |ВЫБРАТЬ
    |   Склад,
    |   Зона,
    |   Номенклатура,
    |   Остаток
    |ИЗ
    |   ВТ_Остатки
    |ИТОГИ
    |   СУММА(Остаток)
    |ПО
    |   Склад,
    |   Зона,
    |   Номенклатура";

Запрос.УстановитьПараметр("ИсходнаяТаблица", ИсходнаяТаблицаЗначений);
РезультатЗапроса = Запрос.Выполнить();

// Создаем результирующее дерево значений
ДеревоРезультат = Новый ДеревоЗначений;
ДеревоРезультат.Колонки.Добавить("Колонка");
ДеревоРезультат.Колонки.Добавить("Остаток", Новый ОписаниеТипов("Число"));

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

В результате мы получаем дерево значений, в котором каждый узел на любом уровне содержит свое значение в одной и той же колонке Колонка, а также агрегированную сумму в колонке Остаток.

Способ 2. Рекурсивное преобразование уже существующего дерева

Если дерево значений уже получено из СКД или внешнего источника и содержит отдельные колонки для каждого уровня, можно выполнить постобработку существующей структуры. Разберем, как объединить значения в одну колонку и удалить лишние поля.

Алгоритм действий следующий:

  1. Добавляем в дерево новую обобщенную колонку, например УзелИерархии.
  2. Рекурсивно обходим все строки дерева. Метод СтрокаДерева.Уровень() позволяет определить глубину текущего узла: 0 — склад, 1 — зона, 2 — номенклатура.
  3. В зависимости от уровня копируем значение в колонку УзелИерархии.
  4. Удаляем исходные колонки группировок из коллекции Колонки дерева значений.

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


Процедура СвернутьИерархиюДерева(СтрокиДерева)
    Для Каждого Стр Из СтрокиДерева Цикл
        УровеньСтроки = Стр.Уровень();
        
        Если УровеньСтроки = 0 Тогда
            Стр.УзелИерархии = Стр.Склад;
        ИначеЕсли УровеньСтроки = 1 Тогда
            Стр.УзелИерархии = Стр.Зона;
        ИначеЕсли УровеньСтроки = 2 Тогда
            Стр.УзелИерархии = Стр.Номенклатура;
        КонецЕсли;
        
        // Рекурсивно обрабатываем подчиненные строки
        Если Стр.Строки.Количество() > 0 Тогда
            СвернутьИерархиюДерева(Стр.Строки);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

После вызова процедуры для корневой коллекции ДеревоЗначений.Строки останется лишь удалить ставшие ненужными колонки:


ДеревоЗначений.Колонки.Добавить("УзелИерархии");
СвернутьИерархиюДерева(ДеревоЗначений.Строки);

ДеревоЗначений.Колонки.Удалить("Склад");
ДеревоЗначений.Колонки.Удалить("Зона");
ДеревоЗначений.Колонки.Удалить("Номенклатура");

Способ 3. Визуальная подмена через событие «ПриВыводеСтроки»

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

Рассмотрим по шагам, как это реализовать:

  1. В элементе управления ТабличноеПоле создаем колонку Иерархия и колонку Остаток. Лишние колонки группировок на форму не добавляем либо удаляем их из коллекции колонок табличного поля.
  2. У колонки Иерархия обязательно устанавливаем признак ОтображатьИерархию = Истина, чтобы платформа отрисовывала элементы управления ветвями дерева («плюсики» и соединительные линии).
  3. Для табличного поля назначаем обработчик события ПриВыводеСтроки.

Пример кода обработчика события формы:


Процедура ТабличноеПолеПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки)
    Если ДанныеСтроки = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    УровеньСтроки = ДанныеСтроки.Уровень();
    
    Если УровеньСтроки = 0 Тогда
        ОформлениеСтроки.Ячейки.Иерархия.Значение = ДанныеСтроки.Склад;
        ОформлениеСтроки.Ячейки.Иерархия.Текст = Строка(ДанныеСтроки.Склад);
    ИначеЕсли УровеньСтроки = 1 Тогда
        ОформлениеСтроки.Ячейки.Иерархия.Значение = ДанныеСтроки.Зона;
        ОформлениеСтроки.Ячейки.Иерархия.Текст = Строка(ДанныеСтроки.Зона);
    ИначеЕсли УровеньСтроки = 2 Тогда
        ОформлениеСтроки.Ячейки.Иерархия.Значение = ДанныеСтроки.Номенклатура;
        ОформлениеСтроки.Ячейки.Иерархия.Текст = Строка(ДанныеСтроки.Номенклатура);
    КонецЕсли;
КонецПроцедуры

Важные особенности настройки ТабличногоПоля в обычных формах

При работе с деревом значений в обычном интерфейсе обратите внимание на следующие нюансы:

  1. Отображение структуры: если у колонки табличного поля не включено свойство ОтображатьИерархию, табличное поле отобразит строки в виде плоского списка без возможности разворачивать и сворачивать ветки.
  2. Метод СоздатьКолонки(): вызов ЭлементыФормы.ТабличноеПоле.СоздатьКолонки() полностью сбрасывает существующие пользовательские настройки и свойства колонок. Любую тонкую настройку (включение иерархии, задание заголовков, ширины) необходимо выполнять строго после этого вызова либо добавлять колонки вручную через Колонки.Добавить().
  3. Раскрытие дерева при открытии: по умолчанию дерево загружается в свернутом виде. Чтобы пользователю сразу были видны ветки, установите свойство табличного поля НачальноеОтображениеДерева в значение НачальноеОтображениеДерева.РаскрыватьВсеУровни или программно обойдите строки, применив метод ЭлементыФормы.ТабличноеПоле.Развернуть().
← На главную