Как быстро программно свернуть или развернуть дерево значений в управляемых формах 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

При этом пользователи замечают странную особенность: если нажать на клавиатуре стандартную комбинацию клавиш Ctrl + Shift + "+", дерево даже с десятками тысяч строк разворачивается практически мгновенно. Почему же встроенный программный метод платформы работает в десятки раз медленнее, чем интерактивное действие пользователя? Проанализируем эту ситуацию, выясним скрытые причины медленной отрисовки и разберем несколько эффективных способов решения проблемы.

Почему стандартный метод Развернуть() работает медленно?

Для начала разберем физику процесса. Когда мы программно обходим дерево значений на клиенте в цикле и для каждой строки вызываем метод Элементы.ИмяДерева.Развернуть(ИдентификаторСтроки), платформа выполняет колоссальный объем лишней работы. При каждом вызове этого метода клиентское приложение пытается пересчитать геометрию элементов формы, обновить полосы прокрутки, перерисовать фокус активности и перепозиционировать текущую строку.

Если в вашем дереве 5000 строк, система выполнит 5000 микро-обновлений интерфейса. Именно эти непрерывные попытки перерисовки графического интерфейса (GUI) на каждом шаге цикла приводят к задержкам, которые пользователь воспринимает как «зависание» программы. Интерактивное же разворачивание через горячие клавиши работает напрямую через движок платформы, который сначала обрабатывает всю структуру данных в памяти и лишь один раз перерисовывает экран.

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

Способ 1. Оптимизация клиентского цикла через отключение текущей строки и видимости

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

  1. Временно сбросим текущую активную строку таблицы формы, установив её в значение Неопределено.
  2. Полностью отключим видимость таблицы дерева значений на форме на время выполнения расчетов.
  3. Запустим цикл развертывания строк.
  4. После окончания цикла вернем на место активную строку и восстановим видимость элемента управления.

Посмотрим на пример практической реализации этого метода:


&НаКлиенте
Процедура РазвернутьДеревоОптимизированно(Команда)

    // 1. Сохраняем идентификатор текущей строки, чтобы не потерять фокус пользователя
    ИдентификаторТекСтроки = Элементы.ДеревоНоменклатуры.ТекущаяСтрока;
    
    // 2. Отключаем текущую строку (снимаем фокус активности)
    Элементы.ДеревоНоменклатуры.ТекущаяСтрока = Неопределено;
    
    // 3. Отключаем видимость элемента формы для блокировки перерисовки интерфейса
    Элементы.ДеревоНоменклатуры.Видимость = Ложь;
    
    // Получаем коллекцию элементов верхнего уровня
    КоллекцияЭлементовДерева = ДеревоНоменклатуры.ПолучитьЭлементы();
    
    // 4. Запускаем обход элементов первого уровня с флагом рекурсивного развертывания поддеревьев
    Для Каждого СтрокаДерева Из КоллекцияЭлементовДерева Цикл
        ИдентификаторСтроки = СтрокаДерева.ПолучитьИдентификатор();
        // Передаем Истина во второй параметр, чтобы развернуть все подчиненные уровни
        Элементы.ДеревоНоменклатуры.Развернуть(ИдентификаторСтроки, Истина);
    КонецЦикла;
    
    // 5. Восстанавливаем исходное состояние интерфейса
    Элементы.ДеревоНоменклатуры.Видимость = Истина;
    Элементы.ДеревоНоменклатуры.ТекущаяСтрока = ИдентификаторТекСтроки;

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

Результат: Отключение видимости таблицы избавляет клиентское приложение от визуального обновления строк на каждом шаге. Скорость выполнения операции на деревьях средней вложенности (до 2000-3000 строк) возрастает в несколько раз.

Способ 2. Серверный лайфхак со свойством «НачальноеОтображениеДерева»

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

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


&НаСервере
Процедура РазвернутьДеревоНаСервере()

    // Устанавливаем режим автоматического раскрытия всех уровней платформой
    Элементы.ДеревоНоменклатуры.НачальноеОтображениеДерева = НачальноеОтображениеДерева.РаскрыватьВсеУровни;
    
    // Чтобы платформа применила новое свойство отображения к уже существующим данным,
    // выполняем быструю перезапись реквизита формы через прикладной объект
    ВременноеДерево = РеквизитФормыВЗначение("ДеревоНоменклатуры");
    ЗначениеВРеквизитФормы(ВременноеДерево, "ДеревоНоменклатуры");

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

Как это работает: Метод РеквизитФормыВЗначение преобразует данные управляемой формы в объект дерева значений в памяти сервера, а обратный метод ЗначениеВРеквизитФормы возвращает их обратно. Платформа воспринимает это как полную перезагрузку данных таблицы и мгновенно отрисовывает дерево на клиенте уже в полностью раскрытом виде средствами своего внутреннего оптимизированного C++ ядра. Этот способ работает практически мгновенно даже на десятках тысяч строк.

Аналогично можно выполнить и быстрое сворачивание дерева, установив свойство в значение НачальноеОтображениеДерева.РаскрыватьПервыйУровень или СворачиватьВсеУровни.

Способ 3. Эмуляция нажатия горячих клавиш через Windows Script Host

Если ваше решение работает исключительно в тонком или толстом клиенте под управлением операционной системы Windows, можно использовать системную эмуляцию нажатия клавиш. Поскольку встроенные обработчики платформы 1С для комбинации Ctrl + Shift + "+" оптимизированы идеально, мы можем программно сымитировать это действие пользователя с помощью COM-объекта WScript.Shell.

Разберем по шагам код такой процедуры:


&НаКлиенте
Процедура РазвернутьДеревоГорячимиКлавишами(Команда)

    // 1. Обязательно переводим фокус активности на элемент управления дерева значений
    ЭтаФорма.ТекущийЭлемент = Элементы.ДеревоНоменклатуры;
    
    // 2. Вызываем эмуляцию нажатия клавиш операционной системы
    Попытка
        Шелл = Новый COMОбъект("Wscript.Shell");
        
        // Передаем комбинацию клавиш: 
        // ^ означает Ctrl, + означает Shift, {+} означает клавишу "плюс" на клавиатуре
        Шелл.SendKeys("^+{+}"); 
    Исключение
        // Исключение сработает, если код запущен на Linux-клиенте или в Web-клиенте браузера
        ВызватьИсключение "Метод эмуляции клавиатуры поддерживается только в ОС Windows!";
    КонецПопытки;

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

Важное предупреждение: Несмотря на высочайшую скорость работы (дерево разворачивается за доли секунды), этот метод имеет существенные недостатки. Он платформозависим (не будет работать на Linux и macOS) и критичен к фокусу окон. Если в момент выполнения этого кода пользователь решит переключиться на другое приложение (например, в браузер или Excel), команда отправки клавиш уйдет в активное окно сторонней программы, что может вызвать непредсказуемое поведение.

Способ 4. Современный стандарт: динамическая отложенная загрузка (Lazy Loading)

В крупных современных конфигурациях (например, ERP, КА, УТ) разработчики вообще отказываются от идеи выгружать в форму дерево целиком, если в нем содержится огромное количество строк. Вместо этого применяется концепция «ленивой» загрузки данных. Суть её заключается в следующем:

  1. При открытии формы на сервере строятся и выводятся строки только самого первого уровня дерева.
  2. Для каждой выведенной строки проверяется, есть ли у неё в базе подчиненные элементы. Если подчиненные элементы существуют, внутрь этой строки добавляется одна пустая строка-пустышка (например, с текстом "Загрузка..."). Это заставляет платформу нарисовать визуальный значок плюсика [+] рядом со строкой.
  3. У таблицы дерева на форме создается обработчик события ПередРазворачиванием.
  4. Когда пользователь нажимает на плюсик [+], управление переходит в этот обработчик. Программа быстро обращается к серверу, считывает подчиненные элементы только для этой конкретной раскрываемой ветки, удаляет строку-пустышку и вставляет реальные данные.

Давайте посмотрим на упрощенный пример реализации события динамической подгрузки:


&НаКлиенте
Процедура ДеревоНоменклатурыПередРазворачиванием(Элемент, СтрокаИД, Отказ)

    СтрокаДерева = ДеревоНоменклатуры.НайтиПоИдентификатору(СтрокаИД);
    ПодчиненныеЭлементы = СтрокаДерева.ПолучитьЭлементы();
    
    // Проверяем, не были ли данные загружены ранее
    Если ПодчиненныеЭлементы.Количество() = 1 И ПодчиненныеЭлементы[0].Наименование = "Загрузка..." Тогда
        // Отменяем стандартное разворачивание на мгновение, чтобы подгрузить реальные данные
        Отказ = Истина;
        
        // Подгружаем реальные данные с сервера для конкретного узла
        ПодгрузитьПодчиненныеУзлыНаСервере(СтрокаИД);
        
        // Теперь разворачиваем узел уже с готовыми данными
        Элементы.ДеревоНоменклатуры.Развернуть(СтрокаИД);
    КонецЕсли;

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

Этот подход кардинально решает любые проблемы производительности. Форма открывается мгновенно, оперативная память клиента не перегружается лишними строками, а развертывание конкретных узлов происходит плавно и незаметно для пользователя.

Резюме и рекомендации

Мы детально рассмотрели проблему программного развертывания дерева значений и нашли пути её решения. Подведем итоги работы:

← На главную