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

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

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

Попытка заполнить такую колонку простым перебором строк табличной части или обращением к оформлению строк «напрямую» вне специальных событий отрисовки приводит к неудаче. В этой статье мы подробно разберем, почему стандартные циклы не работают для виртуальных колонок, и рассмотрим три надежных способа решения этой проблемы для обычных и управляемых форм.

Выясняем причину: почему не работает прямой перебор?

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


Для Каждого СтрокаТ Из ЭлементыФормы.Товары.Значение Цикл
    ЭлементыФормы.Товары.ОформлениеСтроки(СтрокаТ).Ячейки.ПродажиЗаГод.Значение = 1; // Ошибка или отсутствие результата
КонецЦикла;

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

  1. Отсутствие поля в источнике данных. Элемент управления ТабличноеПоле отображает данные из своего источника (в данном случае — табличной части Товары). Поскольку колонки ПродажиЗаГод в метаданных документа не существует, при обходе коллекции Товары у строки физически нет свойства, куда можно записать значение.
  2. Динамическая визуализация. Коллекция ОформлениеСтроки создается платформой динамически и исключительно для тех строк, которые в данный момент отображаются на экране монитора. Попытка получить оформление строки, которая скрыта (находится ниже видимой области), вызовет ошибку или будет проигнорирована платформой.
  3. Потеря данных при перерисовке. Даже если вам удастся принудительно записать текст в ячейку видимой строки, при первой же перерисовке интерфейса (скроллинг, клик мыши, изменение размера окна) платформа сотрет эти данные, так как у ячейки нет постоянной связи с источником данных.

Попытки выполнять расчеты «на лету» в событиях ПриВыводеСтроки или ПриПолученииДанных без предварительной подготовки ведут к критическому снижению производительности: система будет выполнять тяжелые запросы к базе данных при каждом движении мыши пользователя. Ниже мы разберем правильные архитектурные подходы к решению этой задачи.

Способ 1. Паттерн «Кэш + ПриПолученииДанных» (Рекомендуемый для обычных форм)

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

Разберем реализацию этого механизма по шагам.

Шаг 1. Объявление и инициализация кэша

В самом верху модуля формы документа объявим переменную, которая будет доступна во всех процедурах формы, и инициализируем её при открытии:


Перем КэшПродаж;

Процедура ПриОткрытии()
    КэшПродаж = Новый Соответствие;
КонецПроцедуры

Шаг 2. Расчет данных по кнопке (оптимизированный запрос)

Напишем обработчик нажатия кнопки на форме. Вместо того чтобы делать запросы в цикле по каждой строке, мы соберем всю номенклатуру из табличной части в один массив, выполним один пакетный запрос к базе данных и сохраним результаты в наш кэш. Ключом соответствия будет выступать ссылка на номенклатуру, а значением — количество продаж.


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

Шаг 3. Быстрый вывод данных на экран

Теперь свяжем наш кэш с интерфейсом через обработчик события ПриПолученииДанных табличного поля. Это событие отрабатывает только для отображаемых строк и не создает нагрузку на базу данных, так как мы просто считываем готовые значения из оперативной памяти.


Процедура ТоварыПриПолученииДанных(Элемент, ОформленияСтрок)
    
    Для Каждого ОформлениеСтроки Из ОформленияСтрок Цикл
        
        ДанныеСтроки = ОформлениеСтроки.ДанныеСтроки;
        Если ДанныеСтроки = Неопределено Тогда
            Продолжить;
        КонецЕсли;
        
        // Поиск значения в оперативной памяти по ключу номенклатуры
        ЗначениеИзКэша = КэшПродаж.Получить(ДанныеСтроки.Номенклатура);
        
        Если ЗначениеИзКэша <> Неопределено Тогда
            ОформлениеСтроки.Ячейки.ПродажиЗаГод.ОтображатьТекст = Истина;
            ОформлениеСтроки.Ячейки.ПродажиЗаГод.Текст = Формат(ЗначениеИзКэша, "ЧДЦ=0");
        Иначе
            // Если расчет не производился или продаж не было
            ОформлениеСтроки.Ячейки.ПродажиЗаГод.Текст = "0";
        EndIf;
        
    КонецЦикла;
    
КонецПроцедуры

Способ 2. Подмена источника данных на Таблицу Значений

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

Суть метода заключается в том, что вместо реальной табличной части документа на форму выводится временная ТаблицаЗначений, в которую программно добавляется полноценная колонка. В этом случае мы сможем использовать привычный цикл обхода строк.

Рассмотрим пошаговую схему работы:

  1. При открытии формы выгружаем табличную часть во временную таблицу значений: ТаблицаФормы = Товары.Выгрузить();.
  2. Программно добавляем колонку: ТаблицаФормы.Колонки.Добавить("ПродажиЗаГод", Новый ОписаниеТипов("Число"));.
  3. Назначаем эту таблицу в качестве значения табличного поля на форме: ЭлементыФормы.Товары.Значение = ТаблицаФормы;. Создаем связь колонок.
  4. По кнопке выполняем стандартный цикл обхода, так как колонка теперь физически существует в источнике данных:
    
    Для Каждого СтрокаТ Из ЭлементыФормы.Товары.Значение Цикл
        СтрокаТ.ПродажиЗаГод = ПосчитатьПродажи(СтрокаТ.Номенклатура);
    КонецЦикла;
    
  5. Перед записью или проведением документа производим обратную загрузку данных из временной таблицы в реальную табличную часть документа, предварительно очистив ее.

Способ 3. Как аналогичная задача решается на Управляемых формах (1С 8.2 / 8.3)

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

Посмотрим на пример программного добавления колонки на управляемой форме в процедуре ПриСозданииНаСервере:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Шаг 1. Описываем динамический реквизит для табличной части "Товары"
    МассивРеквизитов = Новый Массив;
    НовыйРеквизит = Новый РеквизитФормы(
        "ПродажиЗаГод",                          // Имя реквизита
        Новый ОписаниеТипов("Число", 10, 0),     // Тип данных
        "Объект.Товары",                         // Путь к родительской коллекции
        "Продажи за год"                         // Заголовок колонки
    );
    МассивРеквизитов.Добавить(НовыйРеквизит);
    
    // Шаг 2. Добавляем реквизит в структуру данных формы на сервере
    ИзменитьРеквизиты(МассивРеквизитов);
    
    // Шаг 3. Создаем визуальный элемент (колонку) в таблице формы
    НоваяКолонка = Элементы.Добавить("ТоварыПродажиЗаГод", Тип("ПолеФормы"), Элементы.Товары);
    НоваяКолонка.Вид = ВидПоляФормы.ПолеВвода;
    НоваяКолонка.ПутьКДанным = "Объект.Товары.ПродажиЗаГод";
    НоваяКолонка.ТолькоПросмотр = Истина;
    
КонецПроцедуры

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


&НаСервере
Процедура РассчитатьПродажиНаСервере()
    
    Для Каждого СтрокаТЧ Из Объект.Товары Цикл
        // Данные пишутся напрямую в строку, платформа сама обновит отображение
        СтрокаТЧ.ПродажиЗаГод = ПолучитьПродажиЗаГод(СтрокаТЧ.Номенклатура);
    КонецЦикла;
    
КонецПроцедуры

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

← На главную