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