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