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