Задача возникла потому, что ... в панельке "Вывести список" не оказалось флажка "С подчинёнными". Решать надо было срочно, вдобавок клиент хотел результат со всеми бантиками условного оформления, а это платформа не умеет. В итоге немного обычного программного создания и настройки СКД, и получилась универсальная механика, пригодная к копипасту и немедленному использованию.
Что умеет: выводит дерево из элемента формы, связанного с реквизитом формы или реквизитом объекта формы, точно сохраняя иерархию и условное оформление.
Чего не умеет: сворачивать группировки, как это в элементе формы; и ещё в этой версии я не публикую вывод данных шапки и подвала дерева (хотя можно сделать). Ну и хитрые пути к данным в разыменованных колонках не факт, что все возьмёт (хотя в таких случаях и сам-то элемент формы не всегда корректно отрабатывает).
Чем ещё интересна - пожалуй, программной работой с СКД и, в частности, соединением набора с самим собой, что описано, например, https://start1c.blogspot.com/2017/01/blog-post.html и на ИС тоже встречалось. Таким образом, всё это чуть допиленный баян.
// Вспомогательная для ВывестиДеревоВТабличныйДокумент
&НаСервере
Процедура РекурсивноЗаполнитьКолонкиПоЭлементам(элфРодитель,рПрефиксПути,рИмяРеквизитаДерева,рКолонкиДерева,рКолонкиТаблицы,соотРеквизитовДерева,рЭлементыВыбораСКД)
// Выводим только видимые колонки
Для каждого элфЭлемент Из элфРодитель.ПодчиненныеЭлементы Цикл
Если не элфЭлемент.Видимость Тогда Продолжить КонецЕсли;
Если ТипЗнч(элфЭлемент)=Тип("ПолеФормы") Тогда
рПутьКДанным=СтрЗаменить(элфЭлемент.ПутьКДанным,рПрефиксПути,"");
Если СтрНачинаетсяС(рПутьКДанным,рИмяРеквизитаДерева+".") Тогда // основная колонка или её разыменование
мИмени=СтрРазделить(рПутьКДанным,".",Ложь);
рИмя=мИмени.Получить(1);
Если рИмя="_ParentKey" или рИмя="_BranchKey" Тогда Продолжить КонецЕсли;
// вносим
колДерева=рКолонкиДерева.Найти(рИмя);
рекДерева=соотРеквизитовДерева.Получить(рИмя);
Если колДерева<>Неопределено и рекДерева<>Неопределено Тогда
рЗаголовок=элфЭлемент.Заголовок; // он приоритетнее, но если пуст, берём из реквизита
Если ПустаяСтрока(рЗаголовок) Тогда
Если мИмени[мИмени.Количество()-1]=рИмя Тогда
рЗаголовок=рекДерева.Заголовок;
Иначе
рЗаголовок=мИмени[мИмени.Количество()-1];
КонецЕсли;
КонецЕсли;
Если ПустаяСтрока(рЗаголовок) Тогда рЗаголовок=рИмя КонецЕсли;
//
Если рКолонкиТаблицы.Найти(рИмя)=Неопределено Тогда
рТипЗначения=колДерева.ТипЗначения; // так более строго и правильно, особенно для конкретизируемых вроде субконто
рКолонкиТаблицы.Добавить(рИмя,рТипЗначения,рЗаголовок);
КонецЕсли;
//
рВыбПоле=рЭлементыВыбораСКД.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
рВыбПоле.Заголовок=рЗаголовок; // а это может быть и поле, и разыменование поля
рВыбПоле.Поле=Новый ПолеКомпоновкиДанных(СтрЗаменить(рПутьКДанным,рИмяРеквизитаДерева+".",""));
рВыбПоле.Использование=Истина;
//
КонецЕсли;
Иначе
// к счастью, коллекция формы не поддерживает поле, чей путь к данным лежит к чему-то вне этой коллекции
// а варианты Строка[0] отработаем в другой версии
КонецЕсли;
// также, в другой версии:
//элфЭлемент.ПутьКДаннымКартинкиСтроки,
//элфЭлемент.ПутьКДаннымПодвала
ИначеЕсли ТипЗнч(элфЭлемент)=Тип("ГруппаФормы") Тогда
рВыбГруппа=рЭлементыВыбораСКД.Добавить(Тип("ГруппаВыбранныхПолейКомпоновкиДанных"));
рВыбГруппа.Заголовок=элфЭлемент.Заголовок;
Если элфЭлемент.Группировка=ГруппировкаКолонок.Вертикальная Тогда
рВыбГруппа.Расположение=РасположениеПоляКомпоновкиДанных.Вертикально;
ИначеЕсли элфЭлемент.Группировка=ГруппировкаКолонок.Горизонтальная Тогда
рВыбГруппа.Расположение=РасположениеПоляКомпоновкиДанных.Горизонтально;
ИначеЕсли элфЭлемент.Группировка=ГруппировкаКолонок.ВЯчейке Тогда
рВыбГруппа.Расположение=РасположениеПоляКомпоновкиДанных.Вместе;
КонецЕсли;
рВыбГруппа.Использование=Истина;
РекурсивноЗаполнитьКолонкиПоЭлементам(элфЭлемент,рПрефиксПути,рИмяРеквизитаДерева,рКолонкиДерева,рКолонкиТаблицы,соотРеквизитовДерева,рВыбГруппа.Элементы);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
// Вспомогательная для ВывестиДеревоВТабличныйДокумент
&НаСервере
Процедура РекурсивноЗаполнитьТаблицуПоДереву(рВетка,тДанных,рКлючРодителя=0)
Для каждого рПодветка Из рВетка.Строки Цикл
стро=тДанных.Добавить();
ЗаполнитьЗначенияСвойств(стро,рПодветка);
стро._ParentKey=рКлючРодителя;
стро._BranchKey=тДанных.Количество();
РекурсивноЗаполнитьТаблицуПоДереву(рПодветка,тДанных,стро._BranchKey);
КонецЦикла;
КонецПроцедуры
// Выводит содержимое дерева значений, размещённого на форме (т.е. коллекцию ДанныеФормыДерево) в табличный документ; возвращает табличный документ.
// При ошибке возвращает пустой таб.документ.
// Содержит внутри себя упакованную рекурсию для заполнения служебной промежуточной таблицы значений.
//
// Параметры:
// ИмяЭлементаФормы - обязательный, строка; имя элемента формы, являющегося выводимым деревом значений;
// ИмяВладельца - необязательный, строка; имя родительского реквизита формы, дочерним к которому может являться дерево значений
// применяется для случаев, когда дерево является реквизитом не формы, а объекта, связанного с формой (и не обязательно основного);
// рВетка, тДанных, рКлюРодителя - служебные параметры для рекурсии, указывать их при вызове функции НЕ следует.
//
&НаСервере
Функция ВывестиДеревоВТабличныйДокумент(ИмяЭлементаФормы, ИмяВладельца="")
Попытка
// собственно основной механизм
т=Новый ТабличныйДокумент;
элфВсёДерево=Элементы.Найти(ИмяЭлементаФормы);
Если элфВсёДерево=Неопределено Тогда
Сообщить("Элемент формы с именем """+ИмяЭлементаФормы+""" не найден!");
Возврат т;
КонецЕсли;
#Область ПолучениеРабочихОбъектовДерева
Если СтрНачинаетсяС(элфВсёДерево.ПутьКДанным,ИмяВладельца+".") Тогда
рПрефиксПути=ИмяВладельца+".";
рИмяРеквизитаДерева=СтрЗаменить(элфВсёДерево.ПутьКДанным,рПрефиксПути,"");
знчВсёДерево=Объект[рИмяРеквизитаДерева];
Иначе
рПрефиксПути="";
рИмяРеквизитаДерева="";
мРеквизитовФормы=ЭтотОбъект.ПолучитьРеквизиты();
Для каждого рекФормы Из мРеквизитовФормы Цикл
Если рекФормы.Имя=элфВсёДерево.ПутьКДанным Тогда рИмяРеквизитаДерева=рекФормы.Имя; Прервать КонецЕсли;
КонецЦикла;
Если ПустаяСтрока(рИмяРеквизитаДерева) Тогда
Сообщить("Реквизит, соответствующий элементу формы с именем """+ИмяЭлементаФормы+""", не найден!");
Возврат т;
КонецЕсли;
знчВсёДерево=ЭтотОбъект[рИмяРеквизитаДерева];
КонецЕсли;
Если ТипЗнч(знчВсёДерево)<>Тип("ДанныеФормыДерево") Тогда
Сообщить("Реквизит, соответствующий элементу формы с именем """+ИмяЭлементаФормы+""", имеет неверный тип (не ""ДанныеФормыДерево"")!");
Возврат т;
КонецЕсли;
рДерево=ДанныеФормыВЗначение(знчВсёДерево,Тип("ДеревоЗначений"));
мРеквизитовДерева=ЭтотОбъект.ПолучитьРеквизиты(элфВсёДерево.ПутьКДанным);
соотРеквизитовДерева=Новый Соответствие;
Для каждого рекДерева Из мРеквизитовДерева Цикл
соотРеквизитовДерева.Вставить(рекДерева.Имя,Новый Структура("Заголовок,Путь,ТипЗначения",рекДерева.Заголовок,рекДерева.Путь,рекДерева.ТипЗначения));
КонецЦикла;
#КонецОбласти
#Область ПервичнаяПодготовка
тДанных=Новый ТаблицаЗначений;
тДанных.Колонки.Добавить("_ParentKey",Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(10,0,ДопустимыйЗнак.Любой)));
тДанных.Колонки.Добавить("_BranchKey",Новый ОписаниеТипов("Число",Новый КвалификаторыЧисла(10,0,ДопустимыйЗнак.Любой)));
рСКД=Новый СхемаКомпоновкиДанных;
//
рИсточникДанных=рСКД.ИсточникиДанных.Добавить();
рИсточникДанных.Имя="ОсновнойИсточник";
рИсточникДанных.ТипИсточникаДанных="Local"; // вариант External для кубов и других БД не делаем
//
рНабор=рСКД.НаборыДанных.Добавить(Тип("НаборДанныхОбъектСхемыКомпоновкиДанных"));
рНабор.Имя="ОсновнойНабор";
рНабор.ИмяОбъекта="ТаблицаИсточник";
рНабор.ИсточникДанных=рИсточникДанных.Имя;
//
рНастройка=рСКД.НастройкиПоУмолчанию;
#КонецОбласти
Для каждого колДерева из рДерево.Колонки Цикл
рекДерева=соотРеквизитовДерева.Получить(колДерева.Имя);
рЗаголовок=рекДерева.Заголовок;
//
Если тДанных.Колонки.Найти(колДерева.Имя)=Неопределено Тогда
рТипЗначения=колДерева.ТипЗначения; // так более строго и правильно, особенно для конкретизируемых вроде субконто
тДанных.Колонки.Добавить(колДерева.Имя,рТипЗначения,рЗаголовок);
КонецЕсли;
КонецЦикла;
// Заполнение колонок таблицы и настроек СКД по элементам формы и дереву
РекурсивноЗаполнитьКолонкиПоЭлементам(элфВсёДерево,рПрефиксПути,рИмяРеквизитаДерева,рДерево.Колонки,тДанных.Колонки,соотРеквизитовДерева,рНастройка.Выбор.Элементы);
// Заполнение строк таблицы по дереву
РекурсивноЗаполнитьТаблицуПоДереву(рДерево,тДанных);
// Заполнение полей набора СКД
Для каждого колТаблицы Из тДанных.Колонки Цикл
рПолеНаб=рНабор.Поля.Добавить(Тип("ПолеНабораДанныхСхемыКомпоновкиДанных"));
рПолеНаб.Заголовок=колТаблицы.Заголовок;
рПолеНаб.Поле=колТаблицы.Имя;
рПолеНаб.ПутьКДанным=колТаблицы.Имя;
// фильтруем "пустые" типы
рОписТипов=колТаблицы.ТипЗначения;
мТипов=Новый Массив;
Для каждого рТип Из рОписТипов.Типы() Цикл
Если рТип=Тип("Неопределено") или рТип=Тип("NULL") или рТип=Неопределено или рТип=Null Тогда Продолжить КонецЕсли;
мТипов.Добавить(рТип);
КонецЦикла;
рПолеНаб.ТипЗначения=Новый ОписаниеТипов(мТипов,рОписТипов.КвалификаторыЧисла,рОписТипов.КвалификаторыСтроки,рОписТипов.КвалификаторыДаты);
КонецЦикла;
// Организация иерархического показа
рСвязь=рСКД.СвязиНаборовДанных.Добавить();
рСвязь.НаборДанныхИсточник=рНабор.Имя;
рСвязь.НаборДанныхПриемник=рНабор.Имя;
рСвязь.ВыражениеИсточник="_BranchKey";
рСвязь.ВыражениеПриемник="_ParentKey";
рСвязь.НачальноеВыражение="0"; // это должно быть вычисляемое значение, результат вычисления - число
рСвязь.УсловиеСвязи="";
// Автовыбранные поля, структура простейшая
рГруппировкаКД=рСКД.НастройкиПоУмолчанию.Структура.Добавить(Тип("ГруппировкаКомпоновкиДанных"));
рГруппировкаКД.Использование=Истина;
рВыбПолеГр=рГруппировкаКД.Выбор.Элементы.Добавить(Тип("АвтоВыбранноеПолеКомпоновкиДанных"));
рВыбПолеГр.Использование=Истина;
#Область НастройкаУсловногоОформления
скдУО=рСКД.НастройкиПоУмолчанию.УсловноеОформление;
Для каждого элУО ИЗ ЭтотОбъект.УсловноеОформление.Элементы Цикл
мПутейПолей=Новый Массив;
Для каждого элПоле Из элУО.Поля.Элементы Цикл
рПутьИмя=Строка(элПоле.Поле);
Если не элПоле.Использование или не СтрНачинаетсяС(рПутьИмя,рИмяРеквизитаДерева) Тогда Продолжить КонецЕсли;
рПутьИмя=СтрЗаменить(рПутьИмя,рИмяРеквизитаДерева,"");
Если ПустаяСтрока(рПутьИмя) Тогда // вносим все поля
Для каждого рВыбПоле Из рСКД.НастройкиПоУмолчанию.Выбор.Элементы Цикл
мПутейПолей.Добавить(Строка(рВыбПоле.Поле));
КонецЦикла;
Иначе
Если мПутейПолей.Найти(рПутьИмя)=Неопределено Тогда
мПутейПолей.Добавить(рПутьИмя);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Если мПутейПолей.Количество()=0 Тогда Продолжить КонецЕсли;
//
элСкдУО=скдУО.Элементы.Добавить();
//
// поля (если некое поле касается всего дерева, то вносим для всех полей)
Для каждого рПутьИмя Из мПутейПолей Цикл
элСкдПоле=элСкдУО.Поля.Элементы.Добавить();
элСкдПоле.Поле=Новый ПолеКомпоновкиДанных(рПутьИмя);
элСкдПоле.Использование=Истина;
КонецЦикла;
//
// само УО
Для каждого рОформление Из элУО.Оформление.Элементы Цикл
Если не рОформление.Использование Тогда Продолжить КонецЕсли;
элСкдУО.Оформление.УстановитьЗначениеПараметра(рОформление.Параметр,рОформление.Значение);
КонецЦикла;
//
// отборы
Для каждого элОтбор Из элУО.Отбор.Элементы Цикл
Если не элОтбор.Использование Тогда Продолжить КонецЕсли;
рПутьКДанным=СтрЗаменить(Строка(элОтбор.ЛевоеЗначение),рПрефиксПути+рИмяРеквизитаДерева+".","");
//
Если ТипЗнч(элОтбор.ПравоеЗначение)=Тип("ПолеКомпоновкиДанных") Тогда
рПравоеЗначениеСкдОтбораУО=Неопределено;
Попытка рПравоеЗначениеСкдОтбораУО=Вычислить(элОтбор.ПравоеЗначение) Исключение КонецПопытки; // на момент вывода дерева
Если рПравоеЗначениеСкдОтбораУО=Неопределено Тогда Продолжить КонецЕсли;
Иначе
рПравоеЗначениеСкдОтбораУО=элОтбор.ПравоеЗначение;
КонецЕсли;
//
элСкдОтбор=элСкдУО.Отбор.Элементы.Добавить(ТипЗнч(элОтбор));
элСкдОтбор.ЛевоеЗначение=Новый ПолеКомпоновкиДанных(рПутьКДанным);
элСкдОтбор.ВидСравнения=элОтбор.ВидСравнения;
элСкдОтбор.ПравоеЗначение=рПравоеЗначениеСкдОтбораУО;
элСкдОтбор.Использование=Истина;
КонецЦикла;
//
ЗаполнитьЗначенияСвойств(элСкдУО,элУО); // общие настройки (в первую очередь области использования)
КонецЦикла;
рСКД.НастройкиПоУмолчанию.ПараметрыВывода.УстановитьЗначениеПараметра("РасположениеРеквизитов",РасположениеРеквизитовКомпоновкиДанных.Отдельно);
#КонецОбласти
#Область ВыводВТабДокумент
компМакета=Новый КомпоновщикМакетаКомпоновкиДанных;
рМакетКомпоновки=компМакета.Выполнить(рСКД,рСКД.НастройкиПоУмолчанию,,,Тип("ГенераторМакетаКомпоновкиДанных"));
//
рПроцессорКД=Новый ПроцессорКомпоновкиДанных;
рПроцессорКД.Инициализировать(рМакетКомпоновки,Новый Структура(рНабор.ИмяОбъекта,тДанных)); // разумеется без внешних функций
//
рПроцессорВывода=Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
рПроцессорВывода.УстановитьДокумент(т);
т=рПроцессорВывода.Вывести(рПроцессорКД,Истина);
#КонецОбласти
Возврат т;
Исключение
Сообщить("ВывестиДеревоВТабличныйДокумент, общая ошибка: "+ОписаниеОшибки());
Возврат Новый ТабличныйДокумент;
КонецПопытки;
КонецФункции
Соответственно, вызов:
табДок=ВывестиДеревоВТабличныйДокумент("МоеДерево","Объект"); // если оно Объект.МоеДерево
табДок=ВывестиДеревоВТабличныйДокумент("МоеДерево"); // если оно реквизит формы
Кому пригодится - хорошо. Тестировалось на 8.3.16.1224, но должно работать и на более ранних. А сама проблема, породившая задачу, наблюдалась на 8.3.17-м релизе. Может, баг, и потом поправят...
Кто наткнётся на косяки - пишите, постараюсь оперативно исправить.