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