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

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

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

Ошибка при вызове метода контекста (ИзменитьРеквизиты): Неуникальное имя реквизита. Имя: "Продукция"

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

Суть проблемы и причины возникновения ошибки

Управляемая форма 1С имеет строгую структуру, разделенную на две составляющие: Данные (Реквизиты) и Интерфейс (Элементы). Когда мы работаем со статической таблицей значений, созданной в конфигураторе, платформа берет управление ее структурой на себя. Но когда мы переходим к динамическому конструированию, вся ответственность за уникальность имен и своевременное уничтожение объектов ложится на плечи программиста.

Ошибка неуникальности имени возникает в двух принципиально разных сценариях:

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

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

Сценарий №1: Инициализация таблицы внутри цикла выборки документов

Проанализируем исходный фрагмент кода, представленный в первом сообщении темы:


&НаСервере
Процедура СоздатьТаблицуНаФормеСервера()
    ДатаНачала = Объект.ДатаВыборка;
    Выборка = Документы.РеализацияПродукции.Выбрать(ДатаНачала);
    
    Пока Выборка.Следующий() Цикл
        Для каждого СтрЗнч Из Выборка.ПрайсЛист Цикл
            ТабВКоде = Новый ТаблицаЗначений;
            // Инициализация колонок...
            ТабВКоде.Колонки.Добавить("Продукция", ОписаниеСтрока);
            
            // Вызов процедуры добавления на форму
            ВывестиТаблицуЗначенийВКодеВТаблицуНаФорме(ТабВКоде, "ТабРеквизит1", "ТабНаФорме1");
        КонецЦикла;
    КонецЦикла;
КонецПроцедуры

В данном примере на каждую итерацию внутреннего цикла по строкам табличной части документа ПрайсЛист создается абсолютно новая таблица значений ТабВКоде. Процедура вывода динамически пытается добавить колонку Продукция к реквизиту формы. На первой же строке второго документа (или даже на второй строке первого) система спотыкается, так как реквизит формы с именем ТабРеквизит1.Продукция уже был создан на предыдущем шаге цикла.

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

Посмотрим на исправленный вариант заполнения:


&НаСервере
Процедура СоздатьТаблицуНаФормеСервера()
    ДатаНачала = Объект.ДатаВыборка;
    Выборка = Документы.РеализацияПродукции.Выбрать(ДатаНачала);
    
    // 1. Инициализируем таблицу в памяти один раз до начала циклов
    ТабВКоде = Новый ТаблицаЗначений;
    ОписаниеСтрока = Новый ОписаниеТипов("Строка");
    ОписаниеЧисло = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3));
    
    ТабВКоде.Колонки.Добавить("Продукция", ОписаниеСтрока, "Продукция");
    ТабВКоде.Колонки.Добавить("Количество", ОписаниеЧисло, "Количество");
    ТабВКоде.Колонки.Добавить("Сумма", ОписаниеЧисло, "Сумма");
    
    // 2. Наполняем таблицу данными в циклах
    Пока Выборка.Следующий() Цикл
        Для каждого СтрЗнч Из Выборка.ПрайсЛист Цикл
            Стр = ТабВКоде.Добавить();
            Стр.Продукция = СтрЗнч.Продукция;
            Стр.Количество = СтрЗнч.Количество;
            Стр.Сумма = СтрЗнч.Сумма;
        КонецЦикла;
    КонецЦикла;
    
    // 3. Выводим готовую таблицу на форму один раз
    ВывестиТаблицуЗначенийВКодеВТаблицуНаФорме(ТабВКоде, "ТабРеквизит1", "ТабНаФорме1");
КонецПроцедуры

Сценарий №2: Ошибка при повторном нажатии (необходимость очистки)

После вынесения инициализации за пределы цикла код отработает корректно ровно один раз. Но стоит пользователю нажать кнопку повторно — система вновь вернет ошибку неуникальности. Проблема в том, что реквизиты, добавленные через метод ИзменитьРеквизиты(), остаются жить в контексте формы до момента её закрытия.

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

  1. Удалить старые элементы управления формы (визуальные колонки).
  2. Удалить старые динамические реквизиты из метаданных формы.
  3. Сгенерировать новые реквизиты и визуальные элементы.

Рассмотрим, как правильно реализовать данный механизм в коде.

Пошаговый алгоритм удаления старых реквизитов и элементов

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

Шаг 1: Удаление визуальных элементов

Для удаления элементов формы используется метод Элементы.Удалить(). Нам необходимо обойти колонки динамической таблицы на форме и удалить их. Безопаснее всего искать элементы по сгенерированным именам.


// Удаляем старые визуальные колонки
Для Каждого Колонка Из ТабВКоде.Колонки Цикл
    ИмяЭлемента = ТабРеквизит + "_" + Колонка.Имя;
    НайденныйЭлемент = Элементы.Найти(ИмяЭлемента);
    Если НайденныйЭлемент <> Неопределено Тогда
        Элементы.Удалить(НайденныйЭлемент);
    КонецЕсли;
КонецЦикла;

Шаг 2: Подготовка массивов для удаления и добавления реквизитов

Метод ИзменитьРеквизиты() принимает два основных параметра: массив добавляемых объектов типа РеквизитФормы и массив строковых путей удаляемых реквизитов. Удаление подчиненных реквизитов (колонок таблицы) требует передачи пути в формате "ИмяТаблицыЗначений.ИмяКолонки".

Проанализируем пример функции, которая корректно выполняет транзакцию перестроения формы:


&НаСервере
Процедура ВывестиТаблицуЗначенийВКодеВТаблицуНаФорме(ТабВКоде, ТабРеквизит, ТабНаФорме)

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

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

    // 3. Выполняем изменение структуры реквизитов одной транзакцией
    // Это критически важно для оптимизации производительности сервера!
    ИзменитьРеквизиты(НовыеРеквизиты, РеквизитыДляУдаления);

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

    // 5. Переносим данные из таблицы значений в реквизит формы
    ЗначениеВРеквизитФормы(ТабВКоде, ТабРеквизит);

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

Важные правила именования динамических реквизитов

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

Ограничения платформы к имени реквизита:

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


&НаСервере
Функция ПолучитьБезопасноеИмяРеквизита(ИсходноеИмя)
    
    // Заменяем недопустимые символы на подчеркивание
    СтрокаИмен = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZабвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ_0123456789";
    Результат = "";
    
    Для Позиция = 1 По СтрДлина(ИсходноеИмя) Цикл
        Символ = Сред(ИсходноеИмя, Позиция, 1);
        Если Найти(СтрокаИмен, Символ) > 0 Тогда
            Результат = Результат + Символ;
        Иначе
            Результат = Результат + "_";
        КонецЕсли;
    КонецЦикла;
    
    // Если имя начинается с цифры, добавим префикс
    ПервыйСимвол = Сред(Результат, 1, 1);
    Если Найти("0123456789", ПервыйСимвол) > 0 Тогда
        Результат = "Колонка_" + Результат;
    КонецЕсли;
    
    Возврат Результат;
КонецФункции

Использование данной функции гарантирует, что даже если в наименовании номенклатуры содержится артикул со спецсимволами вроде "Продукция 15-А/б", программный реквизит формы будет назван корректно (например, "Продукция_15_А_б"), а исходное красивое наименование будет помещено в визуальное свойство Заголовок колонки формы.

Заключение

Мы детально изучили механизмы управления динамической структурой управляемой формы 1С. Подведем итог в виде золотых правил разработчика при работе с динамическими таблицами значений:

  1. Никогда не выполняйте вызовы ИзменитьРеквизиты() внутри циклов выборки данных. Сначала полностью сформируйте и заполните таблицу значений в оперативной памяти сервера, и лишь затем передавайте её на форму.
  2. Перед добавлением новых динамических реквизитов всегда очищайте форму от старых динамических элементов и данных. Удаление должно происходить строго «сверху вниз»: сначала визуальные поля формы, затем — сами реквизиты.
  3. Объединяйте удаление старых реквизитов и добавление новых в один единый вызов метода ИзменитьРеквизиты(). Это минимизирует количество серверных вызовов и обращений к контексту формы, значительно ускоряя работу интерфейса для конечного пользователя.
  4. Всегда следите за чистотой имен реквизитов. При формировании колонок на основе пользовательских данных используйте экранирование спецсимволов.
← На главную