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