Как вывести иерархическое дерево документов в одну колонку на форме 1С?

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

При разработке интерфейсов на платформе 1С:Предприятие часто возникает задача отобразить связанные данные в виде дерева: например, документ-родитель на верхнем уровне, а внутри него — документы или аналитика из его табличной части. Когда разработчик пытается использовать штатную выгрузку результата запроса через РезультатЗапроса.Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией), платформа создает отдельные колонки для каждого поля из секции ВЫБРАТЬ. В результате данные отображаются «лесенкой» в разных столбцах таблицы формы, а требуется компактный вывод всех уровней иерархии в одну общую колонку.

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

Почему штатная выгрузка запроса распределяет данные по двум колонкам

Выясним причину такого поведения. Метод Выгрузить() объекта РезультатЗапроса жестко привязан к структуре полей, описанных в запросе. Если в секции ВЫБРАТЬ присутствуют два разноименных поля — например, ВТ.Ссылка КАК Документ и ПлатежноеПоручениеВходящееРасшифровкаПлатежа.Сделка КАК ДокументПодчиненный, — платформа обязана создать в результирующем дереве значений ровно две колонки с соответствующими именами.

При обходе по группировкам родительский узел заполняет колонку Документ (оставляя ДокументПодчиненный пустым), а дочерние строки заполняют ДокументПодчиненный (оставляя родительскую колонку пустой). Механизма автоматического «склеивания» разноименных полей разных уровней иерархии в единую колонку в объекте РезультатЗапроса не предусмотрено.

Решение: программный обход выборки и формирование дерева

Рассмотрим основной способ решения проблемы: обход результата запроса через объект ВыборкаИзРезультатаЗапроса с ручным заполнением дерева значений, содержащего всего одну колонку.

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


Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
    Группа12 = Группа1.Строки.Добавить();
    // ОШИБКА: обращение к родительской выборке вместо детальной
    Группа12.Документ = ВыборкаСсылка.ДокументПодчиненный; 
КонецЦикла;

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

Посмотрим на правильный и полный вариант заполнения дерева значений:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 3
    |    ПлатежноеПоручениеВходящее.Ссылка КАК Ссылка
    |ПОМЕСТИТЬ ВТ
    |ИЗ
    |    Документ.ПлатежноеПоручениеВходящее КАК ПлатежноеПоручениеВходящее
    |
    |УПОРЯДОЧИТЬ ПО
    |    ПлатежноеПоручениеВходящее.Дата УБЫВ
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    ВТ.Ссылка КАК Документ,
    |    ПлатежноеПоручениеВходящееРасшифровкаПлатежа.Сделка КАК ДокументПодчиненный
    |ИЗ
    |    ВТ КАК ВТ
    |        ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПлатежноеПоручениеВходящее.РасшифровкаПлатежа КАК ПлатежноеПоручениеВходящееРасшифровкаПлатежа
    |        ПО ВТ.Ссылка = ПлатежноеПоручениеВходящееРасшифровкаПлатежа.Ссылка
    |
    |ИТОГИ ПО
    |    Документ";

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

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

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

Пока ВыборкаГруппировка.Следующий() Цикл
    
    СтрокаРодитель = ДеревоЗначений.Строки.Добавить();
    СтрокаРодитель.Документ = ВыборкаГруппировка.Документ;
    
    // Получаем детальные записи текущей группировки
    ВыборкаДетали = ВыборкаГруппировка.Выбрать();
    
    Пока ВыборкаДетали.Следующий() Цикл
        
        // Проверяем на заполненность, так как при левом соединении может вернуться NULL
        Если ЗначениеЗаполнено(ВыборкаДетали.ДокументПодчиненный) Тогда
            СтрокаПодчиненная = СтрокаРодитель.Строки.Добавить();
            СтрокаПодчиненная.Документ = ВыборкаДетали.ДокументПодчиненный;
        КонецЕсли;
        
    КонецЦикла;
    
КонецЦикла;

// Переносим сформированные данные в реквизит управляемой формы
ЗначениеВРеквизитФормы(ДеревоЗначений, "Дерево");

Важные нюансы реализации

Разберем по шагам ключевые технические моменты, которые необходимо учитывать при работе с деревьями на управляемых формах:

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

Альтернативный вариант: прямое заполнение реквизита формы

Рассмотрим способ оптимизации кода на стороне сервера управляемой формы. Чтобы не создавать в оперативной памяти промежуточный тяжелый объект ДеревоЗначений и не тратить ресурсы на его последующую конвертацию методом ЗначениеВРеквизитФормы, можно заполнять коллекцию элементов формы напрямую:


// Очищаем существующие строки дерева на форме
КоллекцияДерева = РеквизитФормыВЗначение("Дерево");
КоллекцияДерева.Строки.Очистить();
ЗначениеВРеквизитФормы(КоллекцияДерева, "Дерево");

// Либо работаем напрямую через элементы реквизита формы:
УзлыФормы = Дерево.ПолучитьЭлементы();
УзлыФормы.Очистить();

ВыборкаГруппировка = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаГруппировка.Следующий() Цикл
    
    СтрокаРодитель = УзлыФормы.Добавить();
    СтрокаРодитель.Документ = ВыборкаГруппировка.Документ;
    
    ВыборкаДетали = ВыборкаГруппировка.Выбрать();
    ПодчиненныеУзлы = СтрокаРодитель.ПолучитьЭлементы();
    
    Пока ВыборкаДетали.Следующий() Цикл
        Если ЗначениеЗаполнено(ВыборкаДетали.ДокументПодчиненный) Тогда
            СтрокаПодчиненная = ПодчиненныеУзлы.Добавить();
            СтрокаПодчиненная.Документ = ВыборкаДетали.ДокументПодчиненный;
        КонецЕсли;
    КонецЦикла;
    
КонецЦикла;

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

← На главную