При разработке на управляемых формах 1С часто возникает нетривиальная задача: необходимо предоставить пользователю удобный интерфейс для ввода данных в виде двумерной матрицы (кросс-таблицы). Например, когда в строках располагаются склады, в колонках — конкретные позиции номенклатуры, а на пересечении ячеек вводится количество товара.
Рассмотрим подробнее, почему прямое добавление колонок в табличную часть (ТЧ) объекта базы данных — это плохая идея, и разберем по шагам правильный архитектурный паттерн реализации такого механизма.
Начинающие разработчики иногда пытаются добавлять новые реквизиты в табличную часть программно при добавлении новой номенклатуры. Проанализируем эту ситуацию: реквизиты объекта базы данных жестко задаются в конфигураторе (метаданные). Изменять метаданные программно «на лету» (так называемое демоническое обновление) в пользовательском режиме крайне не рекомендуется. Это приведет к блокировкам, ошибкам структуры базы данных и является редкостным извратом в архитектуре 1С.
Вместо этого мы применим стандартный подход: линейное хранение данных в базе и матричное отображение на форме.
Разберем концепцию работы. В базе данных информация должна храниться строго линейно. Наша реальная табличная часть в объекте будет состоять всего из трех колонок:
СкладНоменклатураКоличествоА вот на самой управляемой форме мы нарисуем динамическую таблицу, в которую данные будут попадать из нашей линейной ТЧ, преобразовываясь в матрицу. При сохранении объекта мы выполним обратное преобразование. Посмотрим на алгоритм реализации по шагам.
При открытии формы (в обработчике ПриСозданииНаСервере или ПриЧтенииНаСервере) нам необходимо обойти линейные данные объекта и сформировать классическую ТаблицаЗначений. Строками этой таблицы станут наши уникальные склады, а колонками — позиции номенклатуры.
// Примерный алгоритм формирования исходной таблицы
ТаблицаМатрица = Новый ТаблицаЗначений;
ТаблицаМатрица.Колонки.Добавить("Склад", Новый ОписаниеТипов("СправочникСсылка.Склады"));
// Обходим уникальную номенклатуру из линейной ТЧ
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
ИмяКолонки = "Номенклатура_" + СтрЗаменить(СтрокаТЧ.Номенклатура.УникальныйИдентификатор(), "-", "");
Если ТаблицаМатрица.Колонки.Найти(ИмяКолонки) = Неопределено Тогда
ТаблицаМатрица.Колонки.Добавить(ИмяКолонки, Новый ОписаниеТипов("Число"));
КонецЕсли;
КонецЦикла;
Управляемая форма не умеет напрямую отображать объект ТаблицаЗначений. Ей требуются специальные «Реквизиты формы». На этом этапе мы программно создадим массив реквизитов на основе подготовленной таблицы и применим их к форме с помощью метода ИзменитьРеквизиты.
МассивДобавляемыхРеквизитов = Новый Массив;
Для Каждого КолонкаТЗ Из ТаблицаМатрица.Колонки Цикл
// Создаем реквизит, указывая путь к таблице на форме
НовыйРеквизит = Новый РеквизитФормы(КолонкаТЗ.Имя, КолонкаТЗ.ТипЗначения, "ТаблицаНаФорме");
МассивДобавляемыхРеквизитов.Добавить(НовыйРеквизит);
КонецЦикла;
// Применяем изменения к форме
ИзменитьРеквизиты(МассивДобавляемыхРеквизитов);
Созданные реквизиты существуют только в памяти формы. Выясним, как вывести их на экран. Нам необходимо программно создать элементы управления (колонки таблицы) и связать их с нашими новыми реквизитами через свойство ПутьКДанным.
ЭлементТаблица = Элементы.ТаблицаНаФорме;
Для Каждого КолонкаТЗ Из ТаблицаМатрица.Колонки Цикл
ИмяЭлемента = "Элемент_" + КолонкаТЗ.Имя;
// Добавляем поле ввода в таблицу на форме
НовыйЭлемент = Элементы.Добавить(ИмяЭлемента, Тип("ПолеФормы"), ЭлементТаблица);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
// Связываем видимую колонку с реквизитом
НовыйЭлемент.ПутьКДанным = "ТаблицаНаФорме." + КолонкаТЗ.Имя;
// Устанавливаем читаемый заголовок (например, наименование номенклатуры)
НовыйЭлемент.Заголовок = ПолучитьЗаголовокПоИмени(КолонкаТЗ.Имя);
КонецЦикла;
Когда визуальная структура готова, нам нужно заполнить ее данными. Для этого сформированную на первом шаге матрицу (со значениями количеств) мы переносим в созданный реквизит формы.
Используем системный метод ЗначениеВРеквизитФормы. После вызова этого метода пользователь увидит на экране полноценную кросс-таблицу, готовую к редактированию.
Поскольку добавленная нами матрица существует лишь в оперативной памяти интерфейса, перед записью документа (в событии ПередЗаписьюНаСервере) необходимо сохранить введенные пользователем данные обратно в линейную структуру объекта базы данных.
Разберем, как это сделать. Сначала выгрузим данные из реквизита формы в обычную таблицу значений с помощью РеквизитФормыВЗначение. Затем обойдем ее двойным циклом (по строкам и колонкам), отберем ненулевые значения и заполним реальную табличную часть объекта.
ТаблицаМатрица = РеквизитФормыВЗначение("ТаблицаНаФорме");
Объект.Товары.Очистить();
Для Каждого СтрокаМатрицы Из ТаблицаМатрица Цикл
Для Каждого КолонкаМатрицы Из ТаблицаМатрица.Колонки Цикл
// Пропускаем колонку со складом
Если КолонкаМатрицы.Имя = "Склад" Тогда Продолжить; КонецЕсли;
Количество = СтрокаМатрицы[КолонкаМатрицы.Имя];
Если Количество > 0 Тогда
НоваяСтрока = Объект.Товары.Добавить();
НоваяСтрока.Склад = СтрокаМатрицы.Склад;
НоваяСтрока.Номенклатура = ПолучитьНоменклатуруИзИмени(КолонкаМатрицы.Имя);
НоваяСтрока.Количество = Количество;
КонецЕсли;
КонецЦикла;
КонецЦикла;
Подведем итоги: такой подход (часто называемый «транспонированием» или созданием кросс-таблицы) является единственно верным паттерном для решения подобных задач в 1С. Он позволяет организовать удобный для пользователя ввод данных в матричном виде (склады/номенклатура), сохраняя при этом нормализованную, реляционную структуру хранения табличных частей в базе данных. Это гарантирует надежность, отсутствие проблем с блокировками и корректную работу платформы.