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