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

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

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

Анализ проблемы: почему стандартный подход тормозит

Рассмотрим стандартную ситуацию: у нас есть дерево, где в каждой ветке должна отображаться сумма числовых значений её дочерних элементов. Типичная ошибка — запускать полный пересчет всей ветки или всего дерева при каждом изменении значения в листе. Если в одной ветке находится около 1000 элементов, то при изменении всего 10–20 листов программа выполнит десятки тысяч итераций. Ситуация усугубляется, если расчет происходит на сервере, что порождает дорогостоящие контекстные вызовы.

Выясним основную причину медленной работы: выполнение цикла по всем строкам ветки имеет сложность O(n). Если же мы поднимаемся от листа к корню, сложность сокращается до O(log n), где log n — это глубина вложенности дерева (обычно не более 3–5 уровней). Разберем, как реализовать этот подход.

Метод инкрементального пересчета (алгоритм «Дельты»)

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

  1. Запоминаем СтароеЗначение реквизита до его изменения.
  2. Получаем НовоеЗначение после изменения.
  3. Вычисляем Дельта = НовоеЗначение - СтароеЗначение.
  4. Прибавляем полученную Дельта к итоговому значению непосредственного родителя, затем к его родителю и так далее до самого верха иерархии.

Этот метод позволяет обновить итоги по всей вертикали дерева всего за несколько операций сложения, независимо от того, сколько тысяч «соседей» находится в этой ветке.

Где хранить старое значение?

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

Способ 1: Использование скрытой колонки в дереве
Мы можем добавить в данные формы дерева невидимую для пользователя колонку, например, СтароеЗначение. При открытии формы или после завершения очередного расчета мы копируем текущие значения в эту колонку. При изменении данных мы сравниваем текущее значение с тем, что сохранено в СтароеЗначение, выполняем расчет и обновляем «старую» колонку.

Способ 2: Использование переменной модуля формы
Если изменение происходит интерактивно, мы можем использовать событие ПередНачаломИзменения для захвата текущего значения в переменную модуля формы на клиенте.

Проанализируем пример реализации на клиенте:


&НаКлиенте
Перем ЗначениеДоИзменения;

&НаКлиенте
Процедура ДеревоДанныхПередНачаломИзменения(Элемент, Отказ)
    ТекущиеДанные = Элемент.ТекущиеДанные;
    Если ТекущиеДанные <> Неопределено Тогда
        // Запоминаем значение перед тем, как пользователь его отредактирует
        ЗначениеДоИзменения = ТекущиеДанные.Сумма;
    КонецЕсли;
КонецПроцедуры

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

    // Вычисляем разницу
    НовоеЗначение = ТекущиеДанные.Сумма;
    Дельта = НовоеЗначение - ЗначениеДоИзменения;

    // Если изменений нет, ничего не делаем
    Если Дельта = 0 Тогда
        Возврат;
    КонецЕсли;

    // Запускаем подъем по иерархии
    Родитель = ТекущиеДанные.ПолучитьРодителя();
    Пока Родитель <> Неопределено Цикл
        Родитель.Сумма = Родитель.Сумма + Дельта;
        Родитель = Родитель.ПолучитьРодителя();
    КонецЦикла;
    
    // Обновляем базу для следующего изменения
    ЗначениеДоИзменения = НовоеЗначение;
КонецПроцедуры

Массовое изменение данных «машиной»

Если значения в листьях меняются не пользователем, а программным алгоритмом (например, при загрузке данных или автоматическом распределении), подход с событием ПередНачаломИзменения не сработает. В этом случае эффективнее использовать скрытую колонку СтароеЗначение прямо в структуре ДанныеФормыДерево.

Рассмотрим ситуацию, когда «машина» меняет сразу сотни строк. Чтобы интерфейс не «фризился» от постоянных обновлений, рекомендуется придерживаться следующих правил:

Обработка частичных изменений (флаги и галочки)

Часто возникает задача: пересчитывать итоги только по тем веткам, которые отмечены пользователем (например, «галочками»). В этом случае алгоритм дельты дополняется проверкой условия. Если мы меняем лист, который не входит в «активную» зону пересчета, мы просто игнорируем его дельту для родительских узлов.

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

Оптимизация работы с ДанныеФормыДерево

Посмотрим на важные технические моменты при работе с большими деревьями:

1. Иерархический обход: Если вам все же нужно пересчитать всё дерево целиком, всегда используйте рекурсию на сервере с объектом ДеревоЗначений. Работа с ДанныеФормыДерево на клиенте в циклах по тысячам элементов всегда будет медленнее из-за особенностей управления памятью в управляемом приложении.

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

3. Избегайте серверных вызовов в цикле: Никогда не вызывайте &НаСервере процедуры внутри цикла обхода дерева. Это самая частая причина «десятков секунд» ожидания, о которых упоминалось в начале темы.

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

← На главную