Дерево из динамического списка: справочник и ДеревоЗначений

· 9 мин

Содержание

Чтобы динамический список открывался деревом, нужно одно из двух: либо его основная таблица — иерархический справочник, и тогда дерево включается режимом просмотра Ещё → Режим просмотра → Дерево, либо данные не иерархические, и тогда вместо динамического списка на форму ставят реквизит типа ДеревоЗначений и заполняют его в модуле формы.

Разница принципиальная. Динамический список умеет рисовать иерархию только поверх таблицы, у которой эта иерархия заложена в метаданных. Произвольный запрос по плоской таблице деревом не станет, сколько ни настраивай список, — платформе неоткуда взять связь «родитель — потомок».

Ниже — оба рабочих способа с точными настройками, рабочий пример кода для заполнения дерева и разбор того, что ломается чаще всего.

Что умеет и чего не умеет динамический список

Динамический список (ДинамическийСписок) — это реквизит управляемой формы, который читает данные из базы порциями, по мере прокрутки. Он выводит справочники, документы, регистры и произвольные запросы. Иерархию он показывает, но с жёстким условием.

Иерархия работает, когда свойство ОсновнаяТаблица указывает на таблицу, у которой в метаданных задана иерархия:

  • 📁 иерархический справочник (Иерархический = Истина);
  • 📁 план счетов — он иерархичен по природе;
  • 📁 план видов характеристик, если у него включена иерархия.

Для таких таблиц платформа сама знает поле Родитель и признак ЭтоГруппа, поэтому строит дерево без единой строки кода.

Если же данные приходят из регистра, временной таблицы или сложного запроса с группировками, динамический список деревом не покажет. Для этого случая есть второй механизм — реквизит ДеревоЗначений, который вы наполняете сами.

Быстрый тест: откройте свойства списка и посмотрите ОсновнаяТаблица. Если там обычный справочник без иерархии или регистр — режим «Дерево» будет недоступен, и это не ошибка настройки, а ограничение платформы.

Способ 1: дерево на основе иерархического справочника

Это самый дешёвый путь. Он подходит, когда дерево должно повторять иерархию существующего справочника — номенклатуры, подразделений, статей затрат.

Шаг 1. Убедитесь, что справочник иерархический. В конфигураторе откройте справочник, вкладка Иерархия. Свойство Иерархический должно быть включено. Вид иерархии бывает двух типов:

  • Иерархия групп и элементов — группы и элементы это разные сущности (классический случай номенклатуры, где папка не является товаром);
  • Иерархия элементов — родителем может быть любой элемент (подразделения, статьи затрат).

От вида иерархии зависит, появится ли у элементов признак группы ЭтоГруппа.

Шаг 2. Привяжите динамический список к этому справочнику. В свойстве ОсновнаяТаблица формы-списка укажите, например, Справочник.Номенклатура. Если справочник иерархический, платформа подхватит иерархию автоматически.

Шаг 3. Включите дерево на форме. В режиме 1С:Предприятие откройте список и в меню Ещё выберите Режим просмотра. Доступны три варианта:

РежимКак выглядитКогда удобен
Списокплоский список без отступовпоиск по всему справочнику
Иерархический списоквнутри одной группы, с переходом внутрьнавигация по папкам
Деревовсе уровни разворачиваются отступамиобзор всей структуры целиком

Выбор Дерево даёт тот самый результат — раскрывающиеся ветки с треугольниками-переключателями. Пользователь может запомнить режим для себя через сохранение настроек формы.

Если в меню Ещё пункта Режим просмотра нет, значит основная таблица не иерархическая. Вернитесь к шагу 1.

Настройка свойств самого динамического списка

Дерево из справочника иногда нужно урезать или отфильтровать — показать не весь справочник, а ветку или срез. Здесь в игру вступают настройки списка.

Произвольный запрос. Включите свойство ПроизвольныйЗапрос у динамического списка и нажмите Настройка списка... — откроется текст запроса. Важное правило: чтобы дерево сохранилось, ОсновнаяТаблица должна остаться иерархическим справочником. Запрос пишите так, чтобы он читал из той же таблицы и возвращал её ссылку. Тогда платформа продолжает видеть Родитель и строит иерархию.

Пример запроса, который оставляет дерево и подтягивает остаток со склада:

ВЫБРАТЬ
    Номенклатура.Ссылка КАК Ссылка,
    Номенклатура.Наименование КАК Наименование,
    Номенклатура.ЭтоГруппа КАК ЭтоГруппа,
    ОстаткиТоваров.КоличествоОстаток КАК Остаток
ИЗ
    Справочник.Номенклатура КАК Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки КАК ОстаткиТоваров
    ПО ОстаткиТоваров.Номенклатура = Номенклатура.Ссылка

Отбор, сортировка, условное оформление. В настройках списка (Ещё → Настроить список) доступны отбор по полям, сортировка и условное оформление строк — например, подсветить группы жирным. Эти настройки не ломают иерархию: дерево остаётся, меняется только то, какие ветки видны и как они раскрашены.

Чего делать не стоит. Если в произвольном запросе вы перейдёте на выборку из регистра или временной таблицы и потеряете связь с иерархическим справочником в ОсновнаяТаблица, дерево пропадёт — список станет плоским. Это частая причина жалобы «настроил запрос, и иерархия исчезла».

Способ 2: дерево значений, собранное в модуле формы

Когда исходные данные не иерархичны (итоги по регистру, результат сложного запроса, внешние данные), динамический список не поможет. Ставьте на форму реквизит типа ДеревоЗначений и наполняйте его сами.

Шаг 1. Добавьте реквизит формы. В конфигураторе на форме создайте реквизит, тип — ДеревоЗначений. Задайте колонки, например Наименование (строка), Ссылка (ссылочный тип), Сумма (число). Перетащите реквизит на форму — получится таблица, которая умеет разворачиваться.

Шаг 2. Заполните дерево на сервере. На управляемой форме реквизит-дерево во время работы имеет тип ДанныеФормыДерево. Доступ к строкам — через метод ПолучитьЭлементы(). Каждая строка тоже имеет свой ПолучитьЭлементы() — это её потомки.

Рабочий пример: заполняем дерево номенклатуры рекурсивно, группы идут выше элементов.

&НаСервере
Процедура ЗаполнитьВетку(РодительСсылка, СтрокиВетки)

    Запрос = Новый Запрос;
    Запрос.Текст =
    "ВЫБРАТЬ
    |   Т.Ссылка КАК Ссылка,
    |   Т.Наименование КАК Наименование,
    |   Т.ЭтоГруппа КАК ЭтоГруппа
    |ИЗ
    |   Справочник.Номенклатура КАК Т
    |ГДЕ
    |   Т.Родитель = &Родитель
    |УПОРЯДОЧИТЬ ПО
    |   Т.ЭтоГруппа УБЫВ,
    |   Т.Наименование";
    Запрос.УстановитьПараметр("Родитель", РодительСсылка);

    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        Строка = СтрокиВетки.Добавить();
        Строка.Ссылка = Выборка.Ссылка;
        Строка.Наименование = Выборка.Наименование;
        Если Выборка.ЭтоГруппа Тогда
            // рекурсивно добавляем содержимое группы
            ЗаполнитьВетку(Выборка.Ссылка, Строка.ПолучитьЭлементы());
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

&НаСервере
Процедура ЗаполнитьДерево()

    ДеревоНоменклатуры.ПолучитьЭлементы().Очистить();
    КореньСправочника = Справочники.Номенклатура.ПустаяСсылка();
    ЗаполнитьВетку(КореньСправочника, ДеревоНоменклатуры.ПолучитьЭлементы());

КонецПроцедуры

Логика проста: корень справочника — это пустая ссылка в поле Родитель. Процедура выбирает всех прямых потомков заданного родителя, добавляет строки, и для каждой группы вызывает сама себя на уровень глубже. Так собирается полное дерево.

Рекурсивный обход выше делает по одному запросу на каждую группу. Для справочника в сотни элементов это нормально, для десятков тысяч — заметно медленно.

Один запрос вместо множества. Приём, которым пользуются на больших справочниках: прочитать все строки одним запросом в таблицу значений, а дерево собрать уже в памяти, без обращений к базе в цикле. Индекс по полю Родитель ускоряет поиск потомков.

&НаСервере
Процедура ЗаполнитьДеревоБыстро()

    Запрос = Новый Запрос;
    Запрос.Текст =
    "ВЫБРАТЬ
    |   Т.Ссылка КАК Ссылка,
    |   Т.Родитель КАК Родитель,
    |   Т.Наименование КАК Наименование,
    |   Т.ЭтоГруппа КАК ЭтоГруппа
    |ИЗ
    |   Справочник.Номенклатура КАК Т";
    Данные = Запрос.Выполнить().Выгрузить();
    Данные.Индексы.Добавить("Родитель");

    ДеревоНоменклатуры.ПолучитьЭлементы().Очистить();
    ДобавитьПотомков(Справочники.Номенклатура.ПустаяСсылка(),
        ДеревоНоменклатуры.ПолучитьЭлементы(), Данные);

КонецПроцедуры

&НаСервере
Процедура ДобавитьПотомков(Родитель, СтрокиВетки, Данные)

    Отбор = Новый Структура("Родитель", Родитель);
    Для Каждого СтрокаДанных Из Данные.НайтиСтроки(Отбор) Цикл
        Строка = СтрокиВетки.Добавить();
        Строка.Ссылка = СтрокаДанных.Ссылка;
        Строка.Наименование = СтрокаДанных.Наименование;
        Если СтрокаДанных.ЭтоГруппа Тогда
            ДобавитьПотомков(СтрокаДанных.Ссылка, Строка.ПолучитьЭлементы(), Данные);
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

Здесь к базе идёт ровно один запрос. Вся сборка дерева — это поиск по таблице значений в памяти, поэтому время почти не зависит от глубины иерархии. Если и этого мало, остаётся ленивая подгрузка: читать потомков ветки в обработчике её раскрытия.

Шаг 3. Покажите дерево на форме. Таблица формы, связанная с реквизитом-деревом, сама рисует разворачивающиеся уровни. Отступы, треугольники и подсчёт потомков платформа берёт на себя.

Обработка выбора и других событий дерева

Дерево обычно не просто витрина — по строке кликают, выбирают, открывают связанный объект. Реакцию вешают на события таблицы формы.

Основные события, которыми пользуются чаще всего:

  • 🖱 Выбор (Selection) — двойной клик или Enter по строке. Здесь открывают форму элемента или возвращают значение в вызвавшую форму.
  • 📍 ПриАктивизацииСтроки (OnActivateRow) — курсор встал на строку. Удобно, чтобы обновить связанную область формы под текущий элемент.
  • ➕ Перед­Разворачиванием — срабатывает перед раскрытием ветки. Пригождается при ленивой подгрузке: подгрузить потомков ровно в момент раскрытия.

Для динамического списка (способ 1) в обработчике события Выбор текущий элемент лежит в поле ТекущаяСтрока, которое хранит ссылку. Для дерева значений (способ 2) к текущей строке обращаются через Элементы.ДеревоНоменклатуры.ТекущиеДанные — это строка со всеми вашими колонками.

Пример: по двойному клику открываем карточку элемента, но игнорируем клик по группе.

&НаКлиенте
Процедура ДеревоНоменклатурыВыбор(Элемент, Строка, Поле, СтандартнаяОбработка)

    ТекущаяСтрока = Элементы.ДеревоНоменклатуры.ТекущиеДанные;
    Если ТекущаяСтрока = Неопределено Тогда
        Возврат;
    КонецЕсли;

    СтандартнаяОбработка = Ложь;
    ПоказатьЗначение(, ТекущаяСтрока.Ссылка);

КонецПроцедуры

Отключение СтандартнаяОбработка нужно, чтобы платформа не выполнила своё действие по умолчанию поверх вашего.

Сравнение подходов: что выбрать под задачу

Коротко: если данные уже лежат в иерархическом справочнике — берите динамический список, это быстрее и дешевле в поддержке. Если дерево надо собрать из разнородных данных — только дерево значений.

КритерийДинамический списокДерево значений
Источникиерархический справочник / планлюбой: запрос, регистр, внешние данные
Чтение данныхдинамическое, порциямивсё грузится в память формы сразу
Объём данныхподходит для больших справочниковтолько для обозримых объёмов
Кодпочти не нужензаполнение пишете сами
Актуальностьобновляется автоматическинужно перезаполнять вручную

Главный водораздел — объём и источник. Динамический список читает базу лениво и тянет большие справочники без тормозов. Дерево значений держит всё в памяти формы, поэтому для многотысячных структур его не используют без ограничения выборки.

Какой способ выбрать под вашу задачу?

Частые ошибки при выводе дерева

Пункт «Режим просмотра» отсутствует. Основная таблица не иерархическая. Проверьте свойство Иерархический у справочника либо используйте дерево значений.

Иерархия пропала после настройки запроса. В произвольном запросе потеряна связь с иерархическим справочником в ОсновнаяТаблица. Запрос должен читать из того же справочника и возвращать его ссылку.

Дерево значений пустое, хотя код отработал. Частая причина — заполнение шло на сервере в локальную переменную, а не в реквизит формы. Обращайтесь именно к реквизиту формы через ПолучитьЭлементы(), а не к отдельно созданному объекту ДеревоЗначений, который на форму не попадает.

Форма открывается долго. Дерево значений наполняется рекурсивно на тысячах элементов — каждый узел это запрос. Читайте данные одним запросом либо подгружайте ветки лениво по событию раскрытия.

Потомки не разворачиваются. У строки не добавлены дочерние элементы через её собственный ПолучитьЭлементы(). Платформа рисует треугольник раскрытия только если у строки есть потомки.

У групп и элементов перепутан порядок. Без сортировки по ЭтоГруппа группы и элементы идут вперемешку. Добавьте УПОРЯДОЧИТЬ ПО ЭтоГруппа УБЫВ (или нужный вам порядок), чтобы папки шли выше.

Частые вопросы

Можно ли показать деревом документы, а не справочник?
Напрямую — нет: у документов нет иерархии в метаданных. Сгруппировать документы по периоду или контрагенту можно только через дерево значений, собрав его в коде.
Чем режим «Дерево» отличается от «Иерархический список»?
В режиме «Дерево» все уровни раскрываются отступами на одной форме. В «Иерархическом списке» вы видите содержимое одной группы и проваливаетесь внутрь неё, как по папкам. Данные те же, различается навигация.
Как сделать, чтобы список всегда открывался деревом?
Пользователь задаёт режим через Ещё → Режим просмотра, и платформа запоминает его в сохранённых настройках формы для этого пользователя. Жёстко закрепить вид для всех обычно оставляют на усмотрение пользователя, чтобы не ломать привычную навигацию.
Почему дерево значений тормозит на большом справочнике?
Оно целиком грузится в память формы. Для больших объёмов либо ограничьте выборку отбором, либо подгружайте дочерние ветки по событию раскрытия, либо вернитесь к динамическому списку с ленивым чтением.
Можно ли в дереве значений хранить ссылку на объект базы?
Да. Задайте колонке ссылочный тип (например, СправочникСсылка.Номенклатура) и кладите в неё ссылку. Тогда по двойному клику открывается карточка объекта.