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