Дерево значений в 1С 8.3: колонки, уровни, поиск и печать
Содержание
Дерево значений в 1С создаётся одной строкой: Дерево = Новый ДеревоЗначений;. Дальше в него добавляют колонки через Дерево.Колонки.Добавить(), а строки — через Дерево.Строки.Добавить() для верхнего уровня и Строка.Строки.Добавить() для вложенных.
Частая ошибка новичков — пытаться создать дерево значений прямо в клиентском модуле управляемой формы. Тонкий клиент этот тип не знает, и код падает. Дерево собирают на сервере, а на форму передают через реквизит. Ниже — весь путь: от объявления переменной до печати иерархии в табличный документ.
Как создать дерево значений и где его можно использовать
ДеревоЗначений — универсальная коллекция платформы 1С:Предприятие 8.3. Она похожа на таблицу значений, но у каждой строки может быть свой набор подчинённых строк. Число уровней не ограничено.
Где объект доступен:
- ✅ сервер (общие модули, модуль объекта, серверные процедуры формы с
&НаСервере); - ✅ толстый клиент и внешнее соединение;
- ❌ тонкий и веб-клиент — там вместо него работает
ДанныеФормыДерево.
Минимальное объявление в серверной процедуре:
&НаСервере
Процедура ПостроитьДеревоНаСервере()
Дерево = Новый ДеревоЗначений;
КонецПроцедуры
Сразу после создания у дерева нет ни колонок, ни строк. Структура дерева состоит из двух коллекций:
| Свойство | Тип | Что хранит |
|---|---|---|
Дерево.Колонки | КоллекцияКолонокДереваЗначений | Общий набор колонок для всех уровней |
Дерево.Строки | КоллекцияСтрокДереваЗначений | Строки верхнего (корневого) уровня |
Строка.Строки | КоллекцияСтрокДереваЗначений | Подчинённые строки конкретной строки |
Строка.Родитель | СтрокаДереваЗначений | Строка уровнем выше; у корневой — Неопределено |
Колонки у дерева одни на все уровни. Нельзя сделать, чтобы у групп была колонка «Количество», а у элементов её не было. Колонка просто остаётся пустой там, где не нужна.
Колонки дерева значений: имя, тип, заголовок
Метод Колонки.Добавить() принимает четыре параметра: имя, описание типов, заголовок и ширину. Обязателен только первый.
ТипСтрока = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(150));
ТипЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
Дерево.Колонки.Добавить("Наименование", ТипСтрока, "Наименование", 40);
Дерево.Колонки.Добавить("Сумма", ТипЧисло, "Сумма, руб.", 15);
Дерево.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
Зачем указывать тип, если можно не указывать:
- 📊 без типа колонка хранит что угодно, и сортировка по ней работает менее предсказуемо;
- 🔢 метод
Итог()корректно суммирует только числовую колонку; - 🖥️ при передаче на форму типизированная колонка сразу получает правильное поле ввода.
Правила для имени колонки:
- Только буквы, цифры и подчёркивание, первым символом — не цифра.
- Без пробелов:
Сумма НДСвызовет ошибку, нужноСуммаНДС. - Имя уникально в пределах дерева.
Колонку можно удалить или проверить её наличие:
Если Дерево.Колонки.Найти("Сумма") <> Неопределено Тогда
Дерево.Колонки.Удалить("Сумма");
КонецЕсли;
Добавление корневых и подчинённых строк
Дерево.Строки.Добавить() создаёт строку первого уровня и возвращает её. У этой строки есть своя коллекция Строки — в неё и добавляют дочерние элементы.
// Уровень 0 — группа
Группа = Дерево.Строки.Добавить();
Группа.Наименование = "Канцтовары";
// Уровень 1 — элементы группы
Стр = Группа.Строки.Добавить();
Стр.Наименование = "Бумага А4";
Стр.Сумма = 1250;
Стр = Группа.Строки.Добавить();
Стр.Наименование = "Ручки шариковые";
Стр.Сумма = 380;
// Уровень 2 — вложенность внутри элемента
Подстр = Стр.Строки.Добавить();
Подстр.Наименование = "Синие";
Подстр.Сумма = 230;
Полезные методы строки:
| Метод / свойство | Что возвращает |
|---|---|
Стр.Уровень() | Номер уровня, корневые строки — 0 |
Стр.Родитель | Родительская строка или Неопределено |
Стр.Строки.Количество() | Число прямых потомков |
Группа.Строки.Вставить(0) | Новая строка на первой позиции, а не в конце |
Группа.Строки.Удалить(Стр) | Удаляет строку вместе со всеми её потомками |
Дерево.Строки.Очистить() | Очищает всё дерево, колонки остаются |
Удалённую строку восстановить нельзя: вместе с ней уходят все уровни ниже. Если нужно временно убрать ветку, скопируйте данные в отдельную коллекцию.
Дерево с произвольной глубиной
Когда глубина заранее неизвестна (например, иерархия справочника), строки удобно добавлять с поиском родителя через соответствие:
Узлы = Новый Соответствие;
Для Каждого Элемент Из МассивЭлементов Цикл
РодительСтрока = Узлы.Получить(Элемент.Родитель);
Если РодительСтрока = Неопределено Тогда
НоваяСтрока = Дерево.Строки.Добавить();
Иначе
НоваяСтрока = РодительСтрока.Строки.Добавить();
КонецЕсли;
НоваяСтрока.Наименование = Элемент.Наименование;
Узлы.Вставить(Элемент.Ссылка, НоваяСтрока);
КонецЦикла;
Здесь МассивЭлементов должен идти так, чтобы родитель стоял раньше потомков. Для справочника этого добиваются упорядочиванием по иерархии в запросе.
Заполнение дерева значений из запроса с итогами
Вручную дерево строят редко. Чаще его получают из запроса с конструкцией ИТОГИ ПО: платформа сама разложит строки по группировкам.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Номенклатура.Родитель КАК Группа,
| Продажи.Номенклатура КАК Номенклатура,
| СУММА(Продажи.СуммаОборот) КАК Сумма
|ИЗ
| РегистрНакопления.Продажи.Обороты(&Начало, &Конец) КАК Продажи
|СГРУППИРОВАТЬ ПО
| Продажи.Номенклатура.Родитель,
| Продажи.Номенклатура
|ИТОГИ
| СУММА(Сумма)
|ПО
| Группа";
Запрос.УстановитьПараметр("Начало", НачалоМесяца(ТекущаяДата()));
Запрос.УстановитьПараметр("Конец", КонецМесяца(ТекущаяДата()));
Дерево = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);
Что получится:
- 📁 на верхнем уровне — строки групп с итоговой суммой;
- 📄 внутри каждой — детальные записи по номенклатуре;
- 🧱 колонки дерева создаются автоматически по полям запроса, вручную их добавлять не надо.
Регистр Продажи и его поля в примере условные — подставьте свои объекты метаданных. Без ИТОГИ ПО и без параметра ОбходРезультатаЗапроса.ПоГруппировкам метод Выгрузить() вернёт плоскую таблицу значений, а не дерево.
Добавьте ИТОГИ ПО Группа ИЕРАРХИЯ, если группы номенклатуры вложены друг в друга. Тогда дерево повторит всю иерархию справочника, а не только ближайшего родителя.
Поиск и обход узлов дерева значений
У коллекции строк два метода поиска. Оба умеют искать во вложенных уровнях, если передать Истина последним параметром.
// Первая найденная строка или Неопределено
Найденная = Дерево.Строки.Найти("Бумага А4", "Наименование", Истина);
Если Найденная <> Неопределено Тогда
Сообщить("Уровень: " + Найденная.Уровень());
КонецЕсли;
// Все строки по условию — массив
Отбор = Новый Структура("Наименование", "Синие");
МассивСтрок = Дерево.Строки.НайтиСтроки(Отбор, Истина);
Различия:
Найти() | НайтиСтроки() | |
|---|---|---|
| Результат | Одна строка или Неопределено | Массив, возможно пустой |
| Условие | Одно значение по списку колонок | Структура «колонка — значение» |
| Несколько условий | Нет | Да, все поля структуры должны совпасть |
Для обхода всех строк нужна рекурсия: обычный цикл Для Каждого по Дерево.Строки видит только верхний уровень.
Процедура ОбойтиСтроки(КоллекцияСтрок)
Для Каждого Стр Из КоллекцияСтрок Цикл
Сообщить(СтрШаблон("%1%2", ПолучитьОтступ(Стр.Уровень()), Стр.Наименование));
ОбойтиСтроки(Стр.Строки);
КонецЦикла;
КонецПроцедуры
Функция ПолучитьОтступ(Уровень)
Результат = "";
Для Сч = 1 По Уровень Цикл
Результат = Результат + " ";
КонецЦикла;
Возврат Результат;
КонецФункции
// Вызов
ОбойтиСтроки(Дерево.Строки);
Чтобы подняться от найденной строки к корню, идите по свойству Родитель, пока оно не станет Неопределено:
Путь = Найденная.Наименование;
Текущая = Найденная.Родитель;
Пока Текущая <> Неопределено Цикл
Путь = Текущая.Наименование + " / " + Путь;
Текущая = Текущая.Родитель;
КонецЦикла;
Сортировка, итоги и фильтрация строк дерева
Сортировка
Дерево.Строки.Сортировать("Сумма Убыв, Наименование", Истина);
Строки переставляются внутри своего уровня: дочерние строки никогда не «перепрыгнут» к чужому родителю. Второй параметр Истина включает сортировку всех подчинённых уровней, без него упорядочится только верхний.
Итоги по колонке
ВсегоПоДереву = Дерево.Строки.Итог("Сумма", Истина);
ВсегоПоГруппе = Группа.Строки.Итог("Сумма");
С параметром Истина суммируются строки всех уровней. Если в группах уже лежат итоги из запроса, сумма задвоится — тогда считайте только верхний уровень или только листья.
Фильтрация
Отбора, как у таблицы значений через Скопировать() с массивом строк, у дерева нет. Ненужные строки удаляют рекурсивно, а ветку оставляют, если в ней есть хоть одна подходящая строка:
// Возвращает Истина, если в ветке осталась хотя бы одна строка
Функция ОтфильтроватьСтроки(КоллекцияСтрок, МинСумма)
ЕстьПодходящие = Ложь;
Всего = КоллекцияСтрок.Количество();
Для Сч = 1 По Всего Цикл
Стр = КоллекцияСтрок[Всего - Сч];
ЕстьПотомки = ОтфильтроватьСтроки(Стр.Строки, МинСумма);
Если ЕстьПотомки Или Стр.Сумма >= МинСумма Тогда
ЕстьПодходящие = Истина;
Иначе
КоллекцияСтрок.Удалить(Стр);
КонецЕсли;
КонецЦикла;
Возврат ЕстьПодходящие;
КонецФункции
Цикл идёт с конца: Всего фиксируется до удалений, а индекс Всего - Сч проходит от последней строки к нулевой. Прямой цикл Для Каждого с удалением внутри пропускает строки, потому что коллекция сдвигается.
Фильтр меняет само дерево. Если исходные данные ещё понадобятся, сначала постройте дерево заново или выгрузите результат запроса повторно.
Дерево значений на управляемой форме
Чтобы пользователь увидел дерево, на форме нужен реквизит с типом ДеревоЗначений и элемент формы, связанный с ним.
- В редакторе формы на вкладке «Реквизиты» добавьте реквизит, например
ДеревоТоваров, тип —ДеревоЗначений. - Раскройте реквизит и добавьте колонки:
Наименование(Строка),Сумма(Число). - Перетащите реквизит в дерево элементов формы — платформа создаст таблицу с режимом отображения «Дерево».
- В серверной процедуре заполните объект и передайте его в реквизит.
&НаСервере
Процедура ЗаполнитьДеревоНаСервере()
Дерево = РеквизитФормыВЗначение("ДеревоТоваров");
Дерево.Строки.Очистить();
// ... добавление строк, как в примерах выше
ЗначениеВРеквизитФормы(Дерево, "ДеревоТоваров");
КонецПроцедуры
РеквизитФормыВЗначение() берёт пустое дерево с уже настроенными колонками, поэтому добавлять их в коде не нужно. Имена колонок в коде и в реквизите должны совпадать, иначе данные из лишних колонок просто не попадут на форму.
На клиенте работают с ДанныеФормыДерево: строки верхнего уровня даёт ДеревоТоваров.ПолучитьЭлементы(), а у каждой строки — свой ПолучитьЭлементы(). Развернуть все группы после заполнения:
&НаКлиенте
Процедура РазвернутьВсе()
Для Каждого Стр Из ДеревоТоваров.ПолучитьЭлементы() Цикл
Элементы.ДеревоТоваров.Развернуть(Стр.ПолучитьИдентификатор(), Истина);
КонецЦикла;
КонецПроцедуры
Вывод дерева в табличный документ с группировками
Для печати иерархии удобнее всего макет с одной областью и группировки строк табличного документа. Пользователь сможет сворачивать уровни плюсиками слева.
Подготовка макета:
- В обработке или отчёте добавьте макет типа «Табличный документ», имя —
Макет. - Выделите одну строку, задайте ей имя области
Строка. - В ячейках впишите параметры
НаименованиеиСумма, у ячеек установите заполнение «Параметр».
Код вывода:
&НаСервере
Функция ВывестиДерево(Дерево)
ТабДок = Новый ТабличныйДокумент;
Макет = РеквизитФормыВЗначение("Объект").ПолучитьМакет("Макет");
ОбластьСтроки = Макет.ПолучитьОбласть("Строка");
ТабДок.НачатьАвтогруппировкуСтрок();
ВывестиУровень(ТабДок, ОбластьСтроки, Дерево.Строки);
ТабДок.ЗакончитьАвтогруппировкуСтрок();
ТабДок.ПоказатьУровеньГруппировокСтрок(0);
Возврат ТабДок;
КонецФункции
&НаСервере
Процедура ВывестиУровень(ТабДок, ОбластьСтроки, КоллекцияСтрок)
Для Каждого Стр Из КоллекцияСтрок Цикл
ОбластьСтроки.Параметры.Наименование = Стр.Наименование;
ОбластьСтроки.Параметры.Сумма = Стр.Сумма;
ОбластьСтроки.Область("R1C1").Отступ = Стр.Уровень() * 2;
ТабДок.Вывести(ОбластьСтроки, Стр.Уровень());
ВывестиУровень(ТабДок, ОбластьСтроки, Стр.Строки);
КонецЦикла;
КонецПроцедуры
Как это работает:
- 🧩
НачатьАвтогруппировкуСтрок()включает режим, в котором уровень группировки задаёт второй параметрВывести(); - ↔️ свойство
Отступу первой ячейки сдвигает наименование вправо по уровню, иерархия видна даже без плюсиков; - 📂
ПоказатьУровеньГруппировокСтрок(0)сворачивает всё до верхнего уровня, пользователь раскрывает нужные группы сам.
Полученный табличный документ кладут в реквизит формы типа ТабличныйДокумент или открывают методом Показать() в толстом клиенте.
Если отчёт нужен пользователю регулярно, проще собрать его на системе компоновки данных: группировки, итоги и отборы она даёт без ручного обхода дерева. Ручной вывод оправдан, когда дерево собирается кодом из разных источников.