Работа с иерархическими структурами данных — классическая задача в разработке на платформе 1С:Предприятие. Дерево значений незаменимо, когда нужно показать пользователю вложенные каталоги товаров, многоуровневые спецификации, структуру подчиненности подразделений или сложные аналитические отчеты. Однако при выводе больших объемов данных (от нескольких тысяч строк и более) разработчики неизбежно сталкиваются с критической проблемой производительности: стандартный метод программного развертывания дерева Развернуть() начинает работать крайне медленно.
При этом пользователи замечают странную особенность: если нажать на клавиатуре стандартную комбинацию клавиш Ctrl + Shift + "+", дерево даже с десятками тысяч строк разворачивается практически мгновенно. Почему же встроенный программный метод платформы работает в десятки раз медленнее, чем интерактивное действие пользователя? Проанализируем эту ситуацию, выясним скрытые причины медленной отрисовки и разберем несколько эффективных способов решения проблемы.
Для начала разберем физику процесса. Когда мы программно обходим дерево значений на клиенте в цикле и для каждой строки вызываем метод Элементы.ИмяДерева.Развернуть(ИдентификаторСтроки), платформа выполняет колоссальный объем лишней работы. При каждом вызове этого метода клиентское приложение пытается пересчитать геометрию элементов формы, обновить полосы прокрутки, перерисовать фокус активности и перепозиционировать текущую строку.
Если в вашем дереве 5000 строк, система выполнит 5000 микро-обновлений интерфейса. Именно эти непрерывные попытки перерисовки графического интерфейса (GUI) на каждом шаге цикла приводят к задержкам, которые пользователь воспринимает как «зависание» программы. Интерактивное же разворачивание через горячие клавиши работает напрямую через движок платформы, который сначала обрабатывает всю структуру данных в памяти и лишь один раз перерисовывает экран.
Ниже мы подробно рассмотрим несколько альтернативных и оптимизированных подходов, которые позволяют обойти это платформенное ограничение.
Самый простой способ ускорить штатный программный обход дерева — временно запретить платформе тратить ресурсы на позиционирование фокуса и отрисовку графических изменений во время работы цикла. Для этого мы применим комплексную оптимизацию:
Неопределено.Посмотрим на пример практической реализации этого метода:
&НаКлиенте
Процедура РазвернутьДеревоОптимизированно(Команда)
// 1. Сохраняем идентификатор текущей строки, чтобы не потерять фокус пользователя
ИдентификаторТекСтроки = Элементы.ДеревоНоменклатуры.ТекущаяСтрока;
// 2. Отключаем текущую строку (снимаем фокус активности)
Элементы.ДеревоНоменклатуры.ТекущаяСтрока = Неопределено;
// 3. Отключаем видимость элемента формы для блокировки перерисовки интерфейса
Элементы.ДеревоНоменклатуры.Видимость = Ложь;
// Получаем коллекцию элементов верхнего уровня
КоллекцияЭлементовДерева = ДеревоНоменклатуры.ПолучитьЭлементы();
// 4. Запускаем обход элементов первого уровня с флагом рекурсивного развертывания поддеревьев
Для Каждого СтрокаДерева Из КоллекцияЭлементовДерева Цикл
ИдентификаторСтроки = СтрокаДерева.ПолучитьИдентификатор();
// Передаем Истина во второй параметр, чтобы развернуть все подчиненные уровни
Элементы.ДеревоНоменклатуры.Развернуть(ИдентификаторСтроки, Истина);
КонецЦикла;
// 5. Восстанавливаем исходное состояние интерфейса
Элементы.ДеревоНоменклатуры.Видимость = Истина;
Элементы.ДеревоНоменклатуры.ТекущаяСтрока = ИдентификаторТекСтроки;
КонецПроцедуры
Результат: Отключение видимости таблицы избавляет клиентское приложение от визуального обновления строк на каждом шаге. Скорость выполнения операции на деревьях средней вложенности (до 2000-3000 строк) возрастает в несколько раз.
Если дерево действительно огромное, клиентская оптимизация может не спасти. В этом случае мы можем применить архитектурный трюк: заставим саму платформу выполнить развертывание нативно на стороне сервера при инициализации данных.
Для этого используется динамическое переопределение свойства таблицы формы НачальноеОтображениеДерева в сочетании с принудительной перезаписью данных формы. Рассмотрим, как это реализовать:
&НаСервере
Процедура РазвернутьДеревоНаСервере()
// Устанавливаем режим автоматического раскрытия всех уровней платформой
Элементы.ДеревоНоменклатуры.НачальноеОтображениеДерева = НачальноеОтображениеДерева.РаскрыватьВсеУровни;
// Чтобы платформа применила новое свойство отображения к уже существующим данным,
// выполняем быструю перезапись реквизита формы через прикладной объект
ВременноеДерево = РеквизитФормыВЗначение("ДеревоНоменклатуры");
ЗначениеВРеквизитФормы(ВременноеДерево, "ДеревоНоменклатуры");
КонецПроцедуры
Как это работает: Метод РеквизитФормыВЗначение преобразует данные управляемой формы в объект дерева значений в памяти сервера, а обратный метод ЗначениеВРеквизитФормы возвращает их обратно. Платформа воспринимает это как полную перезагрузку данных таблицы и мгновенно отрисовывает дерево на клиенте уже в полностью раскрытом виде средствами своего внутреннего оптимизированного C++ ядра. Этот способ работает практически мгновенно даже на десятках тысяч строк.
Аналогично можно выполнить и быстрое сворачивание дерева, установив свойство в значение НачальноеОтображениеДерева.РаскрыватьПервыйУровень или СворачиватьВсеУровни.
Если ваше решение работает исключительно в тонком или толстом клиенте под управлением операционной системы Windows, можно использовать системную эмуляцию нажатия клавиш. Поскольку встроенные обработчики платформы 1С для комбинации Ctrl + Shift + "+" оптимизированы идеально, мы можем программно сымитировать это действие пользователя с помощью COM-объекта WScript.Shell.
Разберем по шагам код такой процедуры:
&НаКлиенте
Процедура РазвернутьДеревоГорячимиКлавишами(Команда)
// 1. Обязательно переводим фокус активности на элемент управления дерева значений
ЭтаФорма.ТекущийЭлемент = Элементы.ДеревоНоменклатуры;
// 2. Вызываем эмуляцию нажатия клавиш операционной системы
Попытка
Шелл = Новый COMОбъект("Wscript.Shell");
// Передаем комбинацию клавиш:
// ^ означает Ctrl, + означает Shift, {+} означает клавишу "плюс" на клавиатуре
Шелл.SendKeys("^+{+}");
Исключение
// Исключение сработает, если код запущен на Linux-клиенте или в Web-клиенте браузера
ВызватьИсключение "Метод эмуляции клавиатуры поддерживается только в ОС Windows!";
КонецПопытки;
КонецПроцедуры
Важное предупреждение: Несмотря на высочайшую скорость работы (дерево разворачивается за доли секунды), этот метод имеет существенные недостатки. Он платформозависим (не будет работать на Linux и macOS) и критичен к фокусу окон. Если в момент выполнения этого кода пользователь решит переключиться на другое приложение (например, в браузер или Excel), команда отправки клавиш уйдет в активное окно сторонней программы, что может вызвать непредсказуемое поведение.
В крупных современных конфигурациях (например, ERP, КА, УТ) разработчики вообще отказываются от идеи выгружать в форму дерево целиком, если в нем содержится огромное количество строк. Вместо этого применяется концепция «ленивой» загрузки данных. Суть её заключается в следующем:
[+] рядом со строкой.ПередРазворачиванием.[+], управление переходит в этот обработчик. Программа быстро обращается к серверу, считывает подчиненные элементы только для этой конкретной раскрываемой ветки, удаляет строку-пустышку и вставляет реальные данные.Давайте посмотрим на упрощенный пример реализации события динамической подгрузки:
&НаКлиенте
Процедура ДеревоНоменклатурыПередРазворачиванием(Элемент, СтрокаИД, Отказ)
СтрокаДерева = ДеревоНоменклатуры.НайтиПоИдентификатору(СтрокаИД);
ПодчиненныеЭлементы = СтрокаДерева.ПолучитьЭлементы();
// Проверяем, не были ли данные загружены ранее
Если ПодчиненныеЭлементы.Количество() = 1 И ПодчиненныеЭлементы[0].Наименование = "Загрузка..." Тогда
// Отменяем стандартное разворачивание на мгновение, чтобы подгрузить реальные данные
Отказ = Истина;
// Подгружаем реальные данные с сервера для конкретного узла
ПодгрузитьПодчиненныеУзлыНаСервере(СтрокаИД);
// Теперь разворачиваем узел уже с готовыми данными
Элементы.ДеревоНоменклатуры.Развернуть(СтрокаИД);
КонецЕсли;
КонецПроцедуры
Этот подход кардинально решает любые проблемы производительности. Форма открывается мгновенно, оперативная память клиента не перегружается лишними строками, а развертывание конкретных узлов происходит плавно и незаметно для пользователя.
Мы детально рассмотрели проблему программного развертывания дерева значений и нашли пути её решения. Подведем итоги работы:
НачальноеОтображениеДерева. Это самый быстрый и безопасный кроссплатформенный метод.ОбщийМодуль.СтандартныеПодсистемыКлиент.РазвернутьУзлыДерева(), который уже содержит оптимизированные алгоритмы обхода от разработчиков фирмы 1С.