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