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