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