Как программно удалить колонки из таблицы значений на управляемой форме 1С?

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

В процессе разработки прикладных решений на платформе «1С:Предприятие 8.3» программисты часто сталкиваются с необходимостью динамического изменения интерфейса. Типичный сценарий: на форме присутствует таблица значений, состав колонок которой должен меняться в зависимости от внешних условий — например, выбранного пользователем периода (колонки по месяцам, неделям) или состава аналитики.

При попытке реализовать такую логику «в лоб» разработчики часто совершают архитектурные ошибки или сталкиваются с системной ошибкой платформы: «Неуникальное имя реквизита». Выясним причину этой проблемы, разберем по шагам правильный алгоритм удаления и добавления динамических колонок, а также рассмотрим примеры оптимизации производительности формы.

Понимание архитектуры: Реквизиты формы против объектов в памяти

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

  1. Объект «ТаблицаЗначений» в оперативной памяти (на сервере): это классический прикладной объект. Для работы с ним используются стандартные методы коллекции колонок: ТЗ.Колонки.Добавить(), ТЗ.Колонки.Удалить() или ТЗ.Колонки.Очистить().
  2. Реквизит управляемой формы (Данные формы): это структура данных, с которой работает клиентская часть. Напрямую удалить или добавить колонку через коллекцию у реквизита формы невозможно. Любое изменение структуры данных на форме должно происходить через специальный метод платформы ИзменитьРеквизиты().

Если вы просто очистите физическую таблицу значений на сервере, реквизиты формы и визуальные элементы (колонки на экране) никуда не исчезнут. При повторной попытке добавить колонку с тем же именем платформа попытается создать дублирующий реквизит формы, что и приведет к критической ошибке «Неуникальное имя реквизита».

Причина ошибки и базовый алгоритм ее решения

Когда пользователь меняет период (например, переключает просмотр с «Январь» на «Февраль»), код пытается заново создать колонки. Но старые реквизиты формы (созданные для января) все еще зарегистрированы в структуре формы. Платформа не может создать новый реквизит с именем, которое уже занято.

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

  1. Определить, какие динамические колонки уже существуют на форме.
  2. Удалить связанные с ними визуальные элементы (элементы управления формы).
  3. Удалить сами реквизиты данных формы с помощью метода ИзменитьРеквизиты().
  4. Создать новые реквизиты данных формы.
  5. Применить изменения структуры.
  6. Создать новые визуальные элементы формы и связать их с новыми путями к данным.

Рассмотрим подробнее каждый из этих шагов и оптимизируем их для достижения максимального быстродействия.

Шаг 1. Получение списка колонок через ПолучитьРеквизиты()

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

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

МассивРеквизитовТаблицы = ПолучитьРеквизиты("ИмяВашейТаблицы");

Анализируя этот массив, мы можем легко отобрать только те колонки, которые были созданы программно. Чтобы не затронуть постоянные (статические) колонки, добавленные в конфигураторе, программным колонкам рекомендуется давать уникальный префикс, например: ДинКолонка_ИмяКолонки.

Шаг 2. Строгий порядок удаления элементов и реквизитов

При программном перестроении интерфейса крайне важно соблюдать последовательность удаления визуальной части (элементов) и структуры данных (реквизитов):

Сначала мы должны удалить визуальные элементы формы (колонки на экране) из коллекции Элементы с помощью метода Элементы.Удалить(). И только после этого вызывать метод ИзменитьРеквизиты() для удаления самих реквизитов из памяти.

Если нарушить этот порядок и сначала удалить реквизиты, то элементы управления на форме на короткое время останутся «висеть» без привязки к источнику данных (с пустым свойством ПутьКДанным). Это может привести к непредвиденным ошибкам отрисовки интерфейса платформы и аварийному завершению сеанса пользователя.

Шаг 3. Оптимизация производительности: Пакетное выполнение

Метод ИзменитьРеквизиты() является одной из самых «тяжелых» операций на управляемой форме. При его вызове платформа полностью перестраивает структуру формы на сервере, выполняет сериализацию и передает обновленный интерфейс на клиент. Если вызывать этот метод отдельно для удаления старых колонок и отдельно для добавления новых, это вызовет двойную нагрузку на сервер.

Золотое правило разработки: массивы добавляемых и удаляемых реквизитов всегда нужно передавать в один вызов метода:

ИзменитьРеквизиты(МассивДобавляемыхРеквизитов, МассивУдаляемыхРеквизитов);

Такой подход гарантирует, что перестройка формы произойдет ровно один раз, что значительно повысит отзывчивость интерфейса при работе пользователя.

Пример готового решения на сервере

Давайте объединим все правила и рекомендации в один надежный программный модуль. Ниже представлена процедура, которую можно вызывать при изменении периода или любых других параметров формы, требующих перестройки таблицы значений.


&НаСервере
Процедура ПерестроитьДинамическиеКолонкиНаСервере(МассивНовыхПериодов)

    // Имя нашей таблицы значений на форме
    ИмяТаблицыФормы = "ТаблицаПродаж"; 
    ПрефиксДинКолонок = "ДинКолонка_";

    МассивДобавляемыхРеквизитов = Новый Массив;
    МассивУдаляемыхРеквизитов = Новый Массив;

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

    // --- ЭТАП 2: Подготовка новых реквизитов ---
    ТипЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));

    Для Каждого Период Из МассивНовыхПериодов Цикл
        // Формируем уникальное имя реквизита без спецсимволов
        ИмяРеквизита = ПрефиксДинКолонок + Формат(Период, "ДФ=ггггММ"); 
        ЗаголовокРеквизита = Формат(Period, "ДФ='MMMM yyyy'");

        НовыйРеквизит = Новый РеквизитФормы(ИмяРеквизита, ТипЧисло, ИмяТаблицыФормы, ЗаголовокРеквизита);
        МассивДобавляемыхРеквизитов.Добавить(НовыйРеквизит);
    КонецЦикла;

    // --- ЭТАП 3: Единовременное изменение структуры данных формы ---
    // Вызываем метод пакетно для удаления старого и создания нового
    ИзменитьРеквизиты(МассивДобавляемыхРеквизитов, МассивУдаляемыхРеквизитов);

    // --- ЭТАП 4: Создание новых визуальных элементов на форме ---
    Для Каждого Реквизит Из МассивДобавляемыхРеквизитов Цикл
        ИмяНовогоЭлемента = ИмяТаблицыФормы + Реквизит.Имя;
        РодительЭлемента = Элементы[ИмяТаблицыФормы];
        
        НоваяКолонка = Элементы.Добавить(ИмяНовогоЭлемента, Тип("ПолеФормы"), РодительЭлемента);
        НоваяКолонка.Вид = ВидПоляФормы.ПолеВвода;
        
        // Связываем элемент формы с реквизитом данных
        НоваяКолонка.ПутьКДанным = ИмяТаблицыФормы + "." + Реквизит.Имя;
        НоваяКолонка.ПоложениеЗаголовка = ПоложениеЗаголовкаЭлементаФормы.Нет;
    КонецЦикла;

КонецПроцедуры

Ограничение платформы, о котором нужно знать

Метод ИзменитьРеквизиты() накладывает жесткое системное ограничение: программно можно удалять только те реквизиты и элементы формы, которые ранее были созданы программно. Если вы попытаетесь передать в массив удаления имя колонки, которая была добавлена статически в конфигураторе (визуальном редакторе формы), платформа выдаст ошибку прав доступа или системный сбой.

Альтернативный подход: Управление видимостью статических колонок

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

В этом случае гораздо эффективнее использовать альтернативный паттерн проектирования:

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

Посмотрим на пример реализации такого подхода:


&НаСервере
Процедура НастроитьВидимостьКолонокПоКварталу(НомерКвартала)

    // Определяем, какие месяцы должны быть видны
    ГраницыМесяцев = Новый Соответствие;
    ГраницыМесяцев.Вставить(1, Новый Массив); // 1-й квартал: Январь, Февраль, Март
    ГраницыМесяцев[1].Добавить("Январь");
    ГраницыМесяцев[1].Добавить("Февраль");
    ГраницыМесяцев[1].Добавить("Март");
    
    // Аналогично заполняются остальные кварталы...
    
    АктивныеМесяцы = ГраницыМесяцев[НомерКвартала];
    
    // Перебираем все возможные колонки месяцев и регулируем их видимость
    ВсеМесяцы = Новый Массив;
    ВсеМесяцы.Добавить("Январь");
    ВсеМесяцы.Добавить("Февраль");
    ВсеМесяцы.Добавить("Март");
    // ... список остальных месяцев
    
    Для Каждого Месяц Из ВсеМесяцы Цикл
        ИмяЭлемента = "ТаблицаПродаж" + Месяц;
        ЭлементКолонки = Элементы.Найти(ИмяЭлемента);
        
        Если ЭлементКолонки <> Неопределено Тогда
            // Устанавливаем Истина, если месяц входит в активный квартал, иначе Ложь
            ЭлементКолонки.Видимость = (АктивныеМесяцы.Найти(Месяц) <> Неопределено);
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

Преимущества альтернативного подхода:

Выбирайте динамическое создание колонок через ИзменитьРеквизиты() только тогда, когда набор колонок действительно непредсказуем (например, зависит от динамического списка контрагентов или номенклатурных групп, заведенных пользователями в базу данных).

← На главную