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