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