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