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