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

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

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

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

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

Давайте по шагам разберем всю цепочку действий — от теории к эталонному коду.


Понимаем архитектуру: Реквизиты против Элементов

Прежде чем писать код, важно зафиксировать базовое правило управляемых форм:

  1. Реквизиты формы (Данные) — это «невидимая» часть формы, ее контекст. Дерево значений на форме хранится в объекте с типом ДанныеФормыДерево. Изменение структуры его колонок выполняется только на сервере с помощью специального метода формы ИзменитьРеквизиты().
  2. Элементы формы (Визуализация) — это то, что видит пользователь на экране. Таблица дерева на форме состоит из подчиненных элементов типа ПолеФормы с видом ПолеВвода. Они связываются с реквизитами через свойство ПутьКДанным.

Если вы удалите реквизит, но забудете удалить элемент формы, платформа выдаст критическую ошибку, так как визуальное поле потеряет связь с данными. Если вы попытаетесь перезаписать структуру реквизитов «на лету» без предварительной очистки элементов, вы получите ошибку дублирования имен. Следовательно, залог успеха — строгий порядок действий.


Шаг 1. Безопасное удаление старых элементов формы

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

Для безопасного удаления мы сначала собираем элементы во временный массив, а затем удаляем их из формы:


// Получаем ссылку на таблицу формы
ТаблицаФормы = Элементы.ФДЗ_РезАнализа; 

// Собираем визуальные колонки в массив для удаления
МассивДляУдаления = Новый Массив;
Для Каждого Элемент Из ТаблицаФормы.ПодчиненныеЭлементы Цикл
    МассивДляУдаления.Добавить(Элемент);
КонецЦикла;

// Удаляем элементы из интерфейса формы
Для Каждого Элемент Из МассивДляУдаления Цикл
    Элементы.Удалить(Элемент);
КонецЦикла;

Шаг 2. Сбор старых реквизитов формы для удаления

Теперь нам нужно очистить структуру данных самого реквизита формы (например, ДЗ_РезАнализа). Чтобы узнать, какие колонки сейчас в нем присутствуют, начинающие разработчики часто преобразуют реквизит в прикладной объект с помощью тяжелой функции РеквизитФормыВЗначение(). Но делать это исключительно ради получения структуры колонок неоптимально.

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


ИмяРеквизитаДерева = "ДЗ_РезАнализа";
МассивУдаляемыхРеквизитов = Новый Массив;

// Получаем массив реквизитов, вложенных в дерево значений
СтарыеКолонки = ПолучитьРеквизиты(ИмяРеквизитаДерева);
Для Каждого ТекРеквизит Из СтарыеКолонки Цикл
    // Путь к реквизиту указывается через точку от имени родителя
    МассивУдаляемыхРеквизитов.Добавить(ИмяРеквизитаДерева + "." + ТекРеквизит.Имя);
КонецЦикла;

Шаг 3. Оптимальное изменение реквизитов (Главный секрет скорости)

Метод ИзменитьРеквизиты() является одним из самых ресурсоемких в платформе 1С. При его вызове сервер полностью перестраивает структуру формы и передает ее описание клиенту.

Частая ошибка разработчиков — вызывать этот метод дважды: сначала для удаления старых колонок, а затем для добавления новых. Это снижает производительность формы в два раза! Метод спроектирован так, чтобы принимать оба действия одновременно за один вызов:

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

Давайте подготовим массив новых колонок на основе дерева значений, полученного, например, из запроса:


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

// Предположим, что ДеревоИсточник — это объект ДеревоЗначений, полученный из запроса
Для Каждого Колонка Из ДеревоИсточник.Колонки Цикл
    НовыйРеквизит = Новый РеквизитФормы(
        Колонка.Имя, 
        Колонка.ТипЗначения, 
        ИмяРеквизитаДерева, 
        Колонка.Заголовок
    );
    МассивДобавляемыхРеквизитов.Добавить(НовыйРеквизит);
КонецЦикла;

// Выполняем ОДИН вызов для удаления старых и добавления новых колонок
ИзменитьРеквизиты(МассивДобавляемыхРеквизитов, МассивУдаляемыхРеквизитов);

Шаг 4. Заполнение реквизита данными и создание элементов формы

После вызова ИзменитьРеквизиты() наше дерево на форме мгновенно обретает новую структуру реквизитов. Теперь мы можем спокойно загрузить в него данные и программно сгенерировать новые элементы формы для отображения колонок на экране.

Обратите внимание на формирование уникального имени элемента формы: рекомендуется использовать шаблон вида ИмяТаблицы_ИмяКолонки. Это гарантирует отсутствие конфликтов имен на форме.


// Заполняем реквизит формы данными дерева
ЗначениеВРеквизитФормы(ДеревоИсточник, ИмяРеквизитаДерева);

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

Полный эталонный шаблон решения

Объединим все шаги в единую, готовую к использованию процедуру, которую можно вызывать на сервере при обновлении или инициализации дерева значений:


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

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

← На главную