Как динамически добавить колонки в Таблицу Значений в 1С и заполнить их данными?

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

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

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

Прежде чем перейти к правильной реализации, давайте выясним причины частых сбоев и разберем ошибки, которые часто встречаются в коде начинающих разработчиков:

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

Правильный алгоритм построения динамической таблицы

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

Шаг 1. Подготовка структуры данных и типов

Сначала нам необходимо определить типы данных, которые будут использоваться в наших динамических колонках. Поскольку мы планируем выводить количество продаж, нам потребуется числовой тип данных с заданной точностью.

Для создания описания типов воспользуемся объектом ОписаниеТипов:


КвалификаторыЧисла = Новый КвалификаторыЧисла(15, 3);
ОписаниеТиповЧисло = Новый ОписаниеТипов("Число", КвалификаторыЧисла);

Шаг 2. Динамическое добавление колонок в структуру ТЗ

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

Синтаксис метода выглядит следующим образом:

Вставить(Индекс, Имя, Тип, Заголовок, Ширина)

Разберем пример кода, который рассчитывает список из 20 прошедших недель и последовательно вставляет новые колонки в нашу таблицу данных:


// Предположим, что ТЗДанных — это наша Таблица Значений, связанная с табличным полем
ТЗДанных = ЭтотОбъект.Товары.Выгрузить(); // Создаем копию или получаем ТЗ

// Будем вставлять колонки начиная с 3-й позиции (индекс 2)
ИндексВставки = 2; 

// Рассчитаем интервал дат для последних 20 недель
ТекущаяДатаДокумента = ТекущаяДата();
Для Шаг = 1 По 20 Цикл
    ПериодНедели = ТекущаяДатаДокумента - (20 - Шаг) * 7 * 86400;
    НомерНедели = НеделяГода(ПериодНедели);
    
    // Формируем корректное имя и понятный заголовок для колонки
    ИмяКолонки = "Неделя_" + Формат(НомерНедели, "ЧГ=0");
    ЗаголовокКолонки = "Нед. " + НомерНедели;
    
    // Проверяем, чтобы колонка не была добавлена дважды
    Если ТЗДанных.Колонки.Найти(ИмяКолонки) = Неопределено Тогда
        ТЗДанных.Колонки.Вставить(ИндексВставки, ИмяКолонки, ОписаниеТиповЧисло, ЗаголовокКолонки, 10);
        ИндексВставки = ИндексВставки + 1; // Сдвигаем индекс для следующей колонки
    КонецЕсли;
КонецЦикла;

Шаг 3. Оптимальное получение данных (Один запрос)

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


МассивНоменклатуры = ТЗДанных.ВыгрузитьКолонку("Номенклатура");

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ПродажиОбороты.Номенклатура КАК Номенклатура,
    |	ПродажиОбороты.Период КАК ПериодНедели,
    |	СУММА(ПродажиОбороты.КоличествоОборот) КАК Количество
    |ИЗ
    |	РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, Неделя, Номенклатура В (&МассивНоменклатуры)) КАК ПродажиОбороты
    |СГРУППИРОВАТЬ ПО
    |	ПродажиОбороты.Номенклатура,
    |	ПродажиОбороты.Период
    |УПОРЯДОЧИТЬ ПО
    |	ПродажиОбороты.Период";

// Устанавливаем период за последние 20 недель
Запрос.УстановитьПараметр("НачалоПериода", НачалоНедели(ТекущаяДатаДокумента - 20 * 7 * 86400));
Запрос.УстановитьПараметр("КонецПериода", КонецНедели(ТекущаяДатаДокумента));
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);

Выборка = Запрос.Выполнить().Выбрать();

Шаг 4. Заполнение созданных колонок данными

Теперь сопоставим полученные из запроса данные продаж с нашими строками в Таблице Значений. Будем использовать быстрый метод поиска строк Найти():


Пока Выборка.Следующий() Цикл
    // Ищем соответствующую строку товара в ТЗ
    СтрокаТЗ = ТЗДанных.Найти(Выборка.Номенклатура, "Номенклатура");
    Если СтрокаТЗ <> Неопределено Тогда
        // Определяем имя динамической колонки по дате продажи
        НомерНедели = НеделяГода(Выборка.ПериодНедели);
        ИмяКолонки = "Неделя_" + Формат(НомерНедели, "ЧГ=0");
        
        // Безопасно записываем значение в динамическую колонку строки
        Попытка
            СтрокаТЗ[ИмяКолонки] = Выборка.Количество;
        Исключение
            // Исключение может возникнуть, если колонка этой недели выходит за рамки созданных 20 колонок
            ЗаписьЖурналаРегистрации("ДинамическиеКолонки", УровеньЖурналаРегистрации.Предупреждение, , , 
                "Не удалось записать данные в динамическую колонку: " + ИмяКолонки);
        КонецПопытки;
    КонецЕсли;
КонецЦикла;

Шаг 5. Визуализация измененных данных на форме

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


// Связываем измененную таблицу значений с реквизитом табличного поля на форме
ЭлементыФормы.ТЗ.Значение = ТЗДанных;

// Заставляем форму перестроить сетку колонок на основе переданного источника данных
ЭлементыФормы.ТЗ.СоздатьКолонки();

Метод СоздатьКолонки() автоматически проанализирует новую структуру таблицы значений ТЗДанных, создаст на форме соответствующие визуальные колонки, расставит их в правильном порядке, применит заголовки и типы данных.

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

← На главную