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

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

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

Постановка задачи и выбор правильного события

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

Для реализации этой механики в обычных формах предусмотрено два основных события табличного поля:

  1. ПриВыводеСтроки — вызывается последовательно для каждой отдельной отображаемой строки.
  2. ПриПолученииДанных — вызывается один раз для всего порционного массива отображаемых на экране строк. Параметр процедуры ОформленияСтрок содержит коллекцию объектов оформления для всех видимых записей.

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

Разберем стандартный алгоритм решения

Разберем по шагам правильную последовательность действий для реализации задачи:

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

Рассмотрим пример кода, реализующий данный подход:


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

КонецПроцедуры

Анализ опасных ошибок и антипаттернов

Проанализируем распространенную ошибку, которую часто допускают начинающие разработчики. Рассмотрим фрагмент кода с ошибкой:


// ПЛОХОЙ ПРИМЕР (НЕИСПОЛЬЗОВАТЬ!):
Для Каждого Стр Из ОформленияСтрок Цикл
    СсылкаСтр = Стр.ДанныеСтроки.Ссылка;
    ОтборДир = Новый Структура("Филиал", СсылкаСтр.Регион);
    
    // Запрос к базе данных выполняется на КАЖДОМ шаге цикла!
    Дир = РегистрыСведений.Алит_ДиректорФил.СрезПоследних(ТекущаяДата(), ОтборДир);
    
    Для Каждого Стр1 Из Дир Цикл
        Стр.Ячейки["ДиректорФилиала"].Значение = Стр1.Директор;
    КонецЦикла;
КонецЦикла;

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

Сложный случай: получение данных на разные даты для каждой строки

Бывают ситуации, когда дата среза должна определяться реквизитом самой строки (например, если у закрытого филиала нужно отображать директора на дату закрытия, а у активного — на текущую дату).

Чтобы не делать запросы в цикле, реализуем задачу через временную таблицу и единый пакетный запрос:


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

КонецПроцедуры

Дополнительные приемы оптимизации и ограничения

Выясним полезные нюансы, которые помогут сделать работу формы максимально быстрой:

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

  1. По такой колонке нельзя установить стандартный отбор средствами формы.
  2. По ней не работает сортировка при клике на заголовок.
  3. По ней не работает встроенный полнотекстовый и стандартный поиск по строке.

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

← На главную