Как оптимизировать раскраску строк в событии ПриПолученииДанных и избежать зависания списка?

Программист IT и автоматизация бизнеса
← На главную

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

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

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

Выясним причину проблемы и проанализируем, какими способами можно построить быстрое и масштабируемое решение.

Вариант 1. Использование скрытой колонки списка (наиболее быстрый и простой способ)

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

Разберем по шагам, как это реализовать:

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

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


Процедура СписокПриПолученииДанных(Элемент, ОформленияСтрок)
    
    Для Каждого ОформлениеСтроки Из ОформленияСтрок Цикл
        
        // Обратите внимание: проверяем значение реквизита напрямую из уже полученных данных строки
        Если ОформлениеСтроки.ДанныеСтроки.ЗапретОбслуживания = Истина Тогда
            ОформлениеСтроки.ЦветФона = WebЦвета.Красный;
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

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

Вариант 2. Пакетный запрос по массиву видимых строк через Соответствие

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

Рассмотрим подробнее алгоритм оптимального запроса:

  1. В первом цикле обходим коллекцию ОформленияСтрок, формируя массив ссылок только тех элементов, которые сейчас отображаются на экране пользователя (обычно от 20 до 50 строк), и заполняем объект Соответствие, где ключом будет ссылка, а значением — сам объект оформления.
  2. Передаем сформированный компактный массив ссылок в параметр запроса с условием Ссылка В (&МассивСсылок).
  3. Обходим результат запроса и моментально находим соответствующее оформление строки по ключу через метод Получить().

Разберем программный код реализации:


Процедура СписокПриПолученииДанных(Элемент, ОформленияСтрок)
    
    // 1. Подготавливаем структуры для сбора только видимых строк
    МассивСсылок = Новый Массив;
    СоответствиеОформлений = Новый Соответствие;
    
    Для Каждого ОформлениеСтроки Из ОформленияСтрок Цикл
        ТекущаяСсылка = ОформлениеСтроки.ДанныеСтроки.Ссылка;
        МассивСсылок.Добавить(ТекущаяСсылка);
        СоответствиеОформлений.Вставить(ТекущаяСсылка, ОформлениеСтроки);
    КонецЦикла;
    
    // Если список пуст, прерываем выполнение
    Если МассивСсылок.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // 2. Выполняем точечный запрос только по отображаемым ссылкам
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    Контрагенты.Ссылка КАК Ссылка
        |ИЗ
        |    Справочник.Контрагенты КАК Контрагенты
        |ГДЕ
        |    Контрагенты.Ссылка В(&МассивСсылок)
        |    И Контрагенты.ЗапретОбслуживания = ИСТИНА";
    
    Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
    Выборка = Запрос.Выполнить().Выбрать();
    
    // 3. Быстро назначаем цвет нужным строкам через поиск по ключу
    Пока Выборка.Следующий() Цикл
        Оформление = СоответствиеОформлений.Получить(Выборка.Ссылка);
        Если Оформление <> Неопределено Тогда
            Оформление.ЦветФона = WebЦвета.Красный;
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

Вариант 3. Дополнительная оптимизация: кэширование и архитектурные решения

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

  1. Кэширование результатов на форме: в модуле формы можно объявить переменную уровня формы КэшСостояний = Новый Соответствие. При наступлении события ПриПолученииДанных в запрос отбираются только те контрагенты, которых еще нет в кэше. Полученные результаты сохраняются в памяти формы, а очистка кэша выполняется только при изменении отборов или ручном обновлении списка пользователем.
  2. Денормализация через регистр сведений: вычисление признаков запрета, долгов или статусов блокировки выносится из формы списка в регламентное задание либо в подписки на события записи документов. Рассчитанный признак записывается в служебный непериодический регистр сведений. В списке остается только быстро соединить видимые строки с готовыми данными этого регистра.
  3. Использование иконок вместо цвета фона: сплошная раскраска строк часто снижает читаемость текста. Гораздо удобнее добавить отдельную колонку с картинкой и устанавливать в ней номер пиктограммы через свойство ОформлениеСтроки.Ячейки.КолонкаКартинки.УстановитьКартинку(), что визуально воспринимается пользователями значительно лучше.
← На главную