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

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

При работе с документами, содержащими табличные части (ТЧ) с большим количеством строк, часто возникает необходимость пересчета итоговых значений после применения различных отборов. Стандартные механизмы платформы не всегда позволяют автоматически пересчитывать итоги, которые отображаются, например, в подвале ТЧ, когда пользователь применяет отбор через горячие клавиши (такие как Ctrl+F). Более того, попытки реализовать такой пересчет клиентским кодом могут приводить к проблемам с производительностью и "дерганью" интерфейса. Давайте подробно рассмотрим эту проблему и возможные пути ее решения, начиная с простого клиентского подхода и заканчивая более оптимальными серверными.

1. Понимание проблемы: Почему возникают "дерганья" при пересчете итогов на клиенте?

Когда мы работаем с управляемыми формами в 1С, существует четкое разделение между клиентской и серверной стороной. Табличные части, особенно если они содержат много строк (тысячи), по умолчанию не загружаются полностью на клиент при открытии формы. Загружается только видимая часть или небольшой буфер. Если мы пытаемся в клиентской процедуре обратиться к полной коллекции строк табличной части документа (например, через Объект.ТЧ), платформа вынуждена загрузить всю табличную часть документа на клиент. Это и является основной причиной замедлений и "дерганья" формы.

Рассмотрим типовую ситуацию: у нас есть документ с табличной частью, в которой более 6000 строк. Мы хотим, чтобы при применении пользователем отбора (например, по колонке "Номенклатура" через Ctrl+F), итоги в подвале табличной части (например, "Количество" и "Сумма") пересчитывались по отфильтрованным данным.

2. Клиентский подход к пересчету и отображению итогов

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

Шаг 1: Добавление реквизитов формы для хранения итогов

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

  1. Создадим реквизит формы с именем СуммаПодвал, тип "Число".
  2. Создадим реквизит формы с именем КоличествоПодвал, тип "Число".

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

Шаг 2: Реализация клиентской процедуры пересчета

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


&НаКлиенте
Процедура ПриОткрытии(Отказ)
    ПодключитьОбработчикОжидания("ОбновитьИтоги", 1);
КонецПроцедуры

&НаКлиенте
Процедура ОбновитьИтоги()
    Количество = 0;
    Сумма = 0;
    
    // Перебираем все строки табличной части документа
    Для Каждого СтрокаТЧ Из Объект.ТЧ Цикл
        // Проверяем, отображается ли строка в текущем отборе на форме
        Если Элементы.ТЧ.ПроверитьСтроку(СтрокаТЧ.ПолучитьИдентификатор()) Тогда
            Количество = Количество + СтрокаТЧ.Количество;
            Сумма = Сумма + СтрокаТЧ.СуммаРегл;
        КонецЕсли;
    КонецЦикла;
    
    // Обновляем реквизиты формы, которые отображаются в подвале
    СуммаПодвал = Сумма;
    КоличествоПодвал = Количество;
КонецПроцедуры
  1. ПриОткрытии(Отказ): В этой процедуре мы подключаем обработчик ожидания ОбновитьИтоги с интервалом в 1 секунду. Это означает, что процедура ОбновитьИтоги будет вызываться каждую секунду, пока форма открыта.

  2. ОбновитьИтоги(): Эта процедура выполняет основной пересчет.

    • Мы инициализируем переменные Количество и Сумма нулями.
    • Далее, мы используем цикл Для Каждого СтрокаТЧ Из Объект.ТЧ Цикл. Здесь кроется ключевая проблема производительности. Обращение к Объект.ТЧ на клиенте принудительно загружает все строки табличной части документа на клиент, если их еще нет.
    • Внутри цикла мы используем метод Элементы.ТЧ.ПроверитьСтроку(СтрокаТЧ.ПолучитьИдентификатор()). Этот метод позволяет определить, видна ли текущая строка табличной части на форме с учетом всех примененных отборов (включая пользовательские через Ctrl+F). Если строка видна, мы добавляем ее значения к нашим итоговым суммам.
    • После цикла мы присваиваем полученные итоги реквизитам формы СуммаПодвал и КоличествоПодвал. Благодаря связи этих реквизитов с полем ПутьКДаннымПодвала в настройках колонки ТЧ, итоги в подвале автоматически обновятся.

Анализ производительности клиентского подхода

Как показал опыт (сообщения 9, 11, 14, 15), при большом количестве строк (более 6000), данный подход приводит к заметным задержкам и "дерганью" формы. Когда форма открывается или пользователь взаимодействует с табличной частью (например, кликает в нее), происходит загрузка данных с сервера на клиент, а затем выполняется цикл по всей ТЧ. Это вызывает:

Основная причина – это полная загрузка всех 6000+ строк на клиентскую сторону для их обработки. Это неэффективно и плохо масштабируется.

3. Оптимизированный подход: Серверный расчет итогов

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

Основные принципы серверного расчета:

  1. Выполнение запроса на сервере: Вместо итерации по строкам ТЧ на клиенте, мы будем выполнять запрос к данным документа на сервере.

  2. Передача параметров отбора: Если мы хотим получить итоги по отфильтрованным данным, нам нужно передать параметры этого отбора на сервер. Это может быть сложной задачей для пользовательского отбора, установленного через Ctrl+F, так как платформа не предоставляет прямого API для получения параметров такого отбора из элемента формы ТЧ.

  3. Обновление реквизита формы: Полученный на сервере итог возвращается на клиент и присваивается реквизиту формы (например, СуммаПодвал), который, как мы уже знаем, связан с подвалом табличной части.

Шаг 1: Изменение логики получения отбора

Для стандартного пользовательского отбора (Ctrl+F) в табличной части элемента формы (Элементы.ТЧ) нет простого способа получить его параметры для передачи на сервер. Сообщение 19 подтверждает, что "других вариантов, увы, нет" для этого сценария. Если требуется серверный расчет, нам придется либо:

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

Шаг 2: Создание серверной функции для расчета итогов

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


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

Разберем этот код:

  1. &НаСервере: Директива указывает, что эта функция выполняется на сервере.

  2. Параметры функции: Функция принимает СсылкаДокумента (для которого считаем итоги) и опциональный параметр ЗначениеОтбора.

  3. Текст запроса: Мы формируем запрос, который выбирает только нужные нам поля (Количество, СуммаРегл) из табличной части документа. Важно, что запрос выполняется к данным, которые хранятся в базе данных, а не к объекту на клиенте.

  4. Условие отбора: Если ЗначениеОтбора указано, мы добавляем условие ДокументТЧ.ПолеДляОтбора = &ЗначениеОтбора в запрос и устанавливаем параметр.

  5. Установка параметра ссылки: Всегда устанавливаем параметр Ссылка, чтобы запрос работал только по текущему документу.

  6. Выполнение и выгрузка: Запрос выполняется, и его результат выгружается в табличную часть (РезультатЗапроса). Обратите внимание, что если строк много, этот результат может быть большим, но он обрабатывается на сервере.

  7. Расчет итогов: Мы используем метод Итог() для табличной части РезультатЗапроса, чтобы быстро получить агрегированные значения. Это гораздо эффективнее, чем цикл по строкам.

  8. Возврат структуры: Функция возвращает структуру с рассчитанными итогами.

Шаг 3: Вызов серверной функции с клиента и обновление формы

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


&НаКлиенте
Процедура ПриИзмененииПоляОтбора(Элемент)
    // Предположим, у нас есть реквизит формы 'ЗначениеОтбораПользователя',
    // который связан с полем ввода для отбора
    ЗначениеОтбора = ЗначениеОтбораПользователя; 
    
    // Вызываем серверную функцию для получения итогов
    Итоги = ПолучитьИтогиПоОтборуНаСервере(Объект.Ссылка, ЗначениеОтбора);
    
    // Обновляем реквизиты формы
    СуммаПодвал = Итоги.Сумма;
    КоличествоПодвал = Итоги.Количество;
КонецПроцедуры

В этой клиентской процедуре:

  1. Мы получаем значение отбора из соответствующего реквизита формы (например, ЗначениеОтбораПользователя).

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

  3. Полученные итоги из структуры присваиваем нашим реквизитам формы СуммаПодвал и КоличествоПодвал, что приведет к обновлению подвала ТЧ.

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

4. Комбинированные и альтернативные подходы

Использование Выгрузить(лкОтбор)

В сообщении 2 было предложено использовать команду лкТЗ = ВашаТЧ.Выгрузить(лкОтбор); СуммаОтбора = лкТЗ.Итог("Сумма");. Этот подход работает на клиенте. Метод Выгрузить() позволяет выгрузить данные табличной части в табличную часть (или таблицу значений), применяя при этом отбор. Однако, если ВашаТЧ - это элемент формы, связанный с Объект.ТЧ, то для выполнения этой операции вся табличная часть все равно сначала будет загружена на клиент, если она еще не загружена. Только после этого к ней будет применен отбор и будет выполнена выгрузка в новую ТЗ. Таким образом, этот метод также страдает от проблемы производительности при работе с очень большими объемами данных на клиенте.

Динамические списки для отображения

Если табличная часть используется исключительно для отображения данных из регистра или сложного запроса, и требуется максимально гибкий и производительный механизм отбора и итогов, можно рассмотреть использование динамических списков. Динамический список может быть основан на произвольном запросе, в котором сразу же можно предусмотреть расчет итогов. Отборы в динамическом списке обрабатываются на сервере. Однако, это усложняет прямую работу с данными документа, если ТЧ является именно объектной ТЧ.

Вывод и рекомендации

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

  1. Для небольших табличных частей (до 1000-2000 строк): Клиентский подход с ПодключитьОбработчикОжидания и Элементы.ТЧ.ПроверитьСтроку() может быть приемлемым, так как полная загрузка ТЧ на клиент не вызовет значительных задержек.

  2. Для больших табличных частей (тысячи строк и более): Настоятельно рекомендуем использовать серверный расчет итогов. Это позволит избежать медленной загрузки всей ТЧ на клиент и значительно повысит производительность.

    • Вам придется реализовать свои элементы управления для задания отбора, чтобы иметь возможность передавать параметры на сервер.
    • Серверная функция будет выполнять запрос к базе данных, агрегируя данные по заданным условиям.
    • Полученные итоги будут возвращены на клиент и присвоены реквизитам формы, связанным с подвалом ТЧ.
  3. Ограничения стандартного отбора (Ctrl+F): Имейте в виду, что для произвольного пользовательского отбора, установленного через Ctrl+F непосредственно в табличной части, получить его параметры на клиенте для передачи на серверную функцию является крайне сложной или невозможной задачей без использования нетиповых механизмов, что делает серверный пересчет по такому отбору труднореализуемым. В таких случаях стоит рассмотреть возможность использования собственных, управляемых полей отбора.

Всегда стремитесь минимизировать объем данных, обрабатываемых на клиенте, особенно при работе с большими коллекциями. Это основной принцип построения высокопроизводительных управляемых форм в 1С.

← На главную