Как преобразовать таблицу значений в дерево значений и правильно вывести его на форму в 1С

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

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

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

Почему не всегда получается построить дерево значений напрямую

Многие разработчики пытаются использовать рекурсивный обход таблицы значений для ручного создания строк дерева. Этот путь сложен, подвержен ошибкам и неэффективен с точки зрения производительности. Наиболее простым и производительным способом является использование механизма запросов 1С, который умеет автоматически выстраивать иерархию справочников с помощью конструкции ИТОГИ ПО ... ИЕРАРХИЯ.

Однако при передаче таблицы значений в запрос разработчики часто сталкиваются со следующими проблемами:

  1. Отсутствие типизации колонок: Если в исходной таблице значений колонки не имеют жестко заданного типа данных, запрос не сможет определить, что поле (например, ссылка на номенклатуру) принадлежит иерархическому справочнику, и иерархия построена не будет.
  2. Пропадание данных на родительских уровнях: Если не указать агрегатные функции для колонок в секции итогов, то данные на уровнях папок (групп) будут отображаться как пустые значения.
  3. Проблемы отображения на форме: Дерево выгружается корректно, но в интерфейсе формы оно выглядит «свернутым» или отображается без колонок.

Проанализируем пошагово, как обойти все эти подводные камни.

Шаг 1. Подготовка исходной таблицы значений

Для того чтобы запрос корректно обработал типы данных, крайне желательно изначально создавать колонки таблицы значений с явным указанием типов. Выясним причину: если колонка имеет неопределенный тип, СУБД не сможет сопоставить ее со справочником базы данных для извлечения родительских элементов.

Рассмотрим пример правильной инициализации исходной таблицы значений:


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

// Задаем типы для числовых показателей
ОписаниеТиповЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
ТЗ.Колонки.Добавить("Продано", ОписаниеТиповЧисло);
ТЗ.Колонки.Добавить("Остаток", ОписаниеТиповЧисло);
ТЗ.Колонки.Добавить("НеобходимыйОбъемЗакупки", ОписаниеТиповЧисло);

Если таблица значений формируется сторонним кодом и изменить определение ее колонок невозможно, мы сможем решить проблему типизации непосредственно внутри запроса с помощью функции ВЫРАЗИТЬ.

Шаг 2. Преобразование таблицы значений в дерево с помощью запроса

Для построения дерева значений мы поместим исходную таблицу во временную таблицу запроса, а затем выберем из нее данные с использованием предложения ИТОГИ ПО ... ИЕРАРХИЯ. Разберем этот код подробно.


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

Запрос.УстановитьПараметр("МояТаблицаЗначений", ТЗ);

РезультатЗапроса = Запрос.Выполнить();

// Выгружаем результат запроса в Дерево Значений с обходом по группировкам с иерархией
ДеревоЗначений = РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);

Важный нюанс: Обратите внимание на секцию ИТОГИ. Если вы просто напишете ПО Номенклатура ИЕРАРХИЯ без указания суммируемых полей, то все числовые колонки на уровне родительских групп будут пустыми (содержать значение NULL). Указание СУММА(ИмяКолонки) позволяет автоматически рассчитать итоги по иерархическим группам номенклатуры сверху вниз.

Шаг 3. Отображение дерева значений на форме

Рассмотрим особенности вывода полученного дерева значений на форму. Алгоритм действий зависит от того, какой тип интерфейса используется в вашей конфигурации.

Вариант А. Вывод в обычных формах

В обычных формах для вывода дерева используется элемент управления Табличное поле с типом значения ДеревоЗначений.

Часто после программного присвоения дерева табличному полю разработчики видят пустую таблицу без структуры. Чтобы элемент формы перерисовал свою структуру под новую таблицу, необходимо вызвать метод СоздатьКолонки().


// Предположим, у нас на форме есть табличное поле с именем ТАБДЕР
ЭлементыФормы.ТАБДЕР.Значение = ДеревоЗначений;

// Принудительно перестраиваем колонки табличного поля на форме
ЭлементыФормы.ТАБДЕР.СоздатьКолонки();

Внимание: Вызов метода СоздатьКолонки() сбрасывает все визуальные настройки колонок (ширину, заголовки, видимость), заданные интерактивно в конфигураторе. Если вы хотите сохранить ручные настройки колонок, не используйте этот метод программно. Вместо этого настройте колонки табличного поля в конфигураторе заранее, а у колонки «Номенклатура» в свойствах обязательно взведите флаг «Отображать иерархию».

Чтобы дерево не выводилось полностью свернутым, мы можем программно раскрыть его ветви. Посмотрим на пример рекурсивного разворота строк:


// Программный разворот всех строк первого уровня дерева на обычной форме
Для Каждого Строка Из ЭлементыФормы.ТАБДЕР.Значение.Строки Цикл 
    // Второй параметр "Истина" указывает на необходимость раскрытия всех подчиненных уровней
    ЭлементыФормы.ТАБДЕР.Развернуть(Строка, Истина); 
КонецЦикла;

Также у табличного поля на форме в конфигураторе можно настроить свойство «Начальное отображение дерева», установив его в значение «Раскрывать все уровни». В этом случае платформа автоматически развернет дерево при инициализации формы.

Вариант Б. Вывод в управляемых формах

Если вы работаете в управляемом интерфейсе (Такси), то напрямую присвоить дерево значений элементу формы нельзя. Процесс состоит из работы с реквизитами формы и элементами интерфейса на сервере и клиенте.

Сначала мы создаем реквизит формы с типом ДеревоЗначений (например, с именем ДеревоНоменклатуры) и перетаскиваем его на форму в виде таблицы. Затем пишем следующий код:


&НаСервере
Процедура ЗаполнитьДеревоНаСервере()
    
    // ... Выполняем запрос с получением дерева значений ДеревоЗначений (код из Шага 2) ...
    
    // Копируем данные из объекта ДеревоЗначений в реквизит управляемой формы
    ЗначениеВРеквизитФормы(ДеревоЗначений, "ДеревоНоменклатуры");
    
КонецПроцедуры

&НаКлиенте
Процедура РазвернутьДеревоНаКлиенте()
    
    // Для разворота дерева на управляемой форме нам нужны идентификаторы строк
    КоллекцияЭлементов = ДеревоНоменклатуры.ПолучитьЭлементы();
    Для Каждого Строка Из КоллекцияЭлементов Цикл
        ИдентификаторСтроки = Строка.ПолучитьИдентификатор();
        // Разворачиваем строку вместе со всеми подчиненными элементами
        Элементы.ДеревоНоменклатуры.Развернуть(ИдентификаторСтроки, Истина);
    КонецЦикла;
    
КонецПроцедуры

Резюме

Мы подробно разобрали универсальный и наиболее производительный алгоритм преобразования плоской таблицы значений в дерево с иерархией. Использование механизма запросов с конструкцией ИТОГИ ПО ... ИЕРАРХИЯ позволяет избежать написания громоздких рекурсивных процедур и минимизировать ошибки в коде. Помните о важности явного приведения типов через ВЫРАЗИТЬ для колонок таблиц-параметров, а также не забывайте использовать методы автоматического или программного разворота строк для удобства конечных пользователей вашего решения.

← На главную