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