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