При разработке сложных интерфейсов на управляемых формах в «1С:Предприятие 8.3» программисты часто прибегают к динамическому изменению состава элементов. Мы добавляем поля ввода, кнопки, декорации и целые группы непосредственно в коде (например, в обработчике ПриСозданииНаСервере). Однако в процессе дальнейшей работы с формой нередко возникает обратная задача: как в коде модуля формы быстро и надежно отличить элемент, созданный программно, от того, который был статически добавлен разработчиком в конфигураторе?
В этой статье мы подробно разберем, почему в платформе нет стандартного флага для таких элементов, проанализируем опасные подходы, которые могут нарушить работу интерфейса, и рассмотрим наиболее эффективные и безопасные способы решения этой задачи.
Выясним сразу: у объектов метаданных элементов формы (таких как ПолеФормы, КнопкаФормы, ГруппаФормы) не существует встроенного свойства вроде СозданПрограммно. Платформа позиционирует все элементы в единой коллекции Элементы одинаково. Поэтому нам придется использовать обходные пути.
Рассмотрим по шагам каждый из возможных вариантов решения.
В процессе обсуждения часто всплывает идея использовать особенность платформенного метода Удалить() коллекции элементов формы. Согласно официальной документации 1С, метод Элементы.Удалить(Элемент) может удалять только ранее программно добавленные элементы. Если попытаться передать туда статический элемент из конфигуратора, платформа вызовет исключение.
На первый взгляд, кажется заманчивым написать простую функцию-эксперт:
// КАТЕГОРИЧЕСКИ НЕ РЕКОМЕНДУЕТСЯ ИСПОЛЬЗОВАТЬ НА ПРАКТИКЕ!
Функция ЭлементСозданПрограммно_ОпасныйМетод(ИмяЭлемента)
Элемент = Элементы.Найти(ИмяЭлемента);
Если Элемент = Неопределено Тогда
Возврат Ложь;
КонецЕсли;
Попытка
Элементы.Удалить(Элемент);
// Если удаление прошло успешно — элемент был программным.
// Но теперь его нет на форме! Нам нужно воссоздавать его заново.
Возврат Истина;
Исключение
// Платформа не разрешила удалить статический элемент
Возврат Ложь;
КонецПопытки;
КонецФункции
Почему этот метод деструктивен и его нельзя использовать?
Если проверяемый элемент действительно был создан программно, метод Удалить() безвозвратно удалит его с формы. При этом вы потеряете:
ПутьКДанным).ПриИзменении, Нажатие и т.д.).Восстановить элемент в первозданном виде программно — крайне ресурсоемкая задача. Поэтому забудьте про этот метод, он подходит только для теоретического понимания ограничений платформы.
Самый простой, быстрый и производительный способ, ставший отраслевым стандартом разработки — это соглашение о наименовании программных элементов. При добавлении элементов кодом всегда давайте им имена со специальным уникальным префиксом. Например: Прогр_, Доп_ или Расш_.
Давайте посмотрим на пример создания такого элемента:
&НаСервере
Процедура ДобавитьПолеВводаНаСервере()
ИмяНовогоПоля = "Прогр_ДополнительноеОписание";
НовоеПоле = Элементы.Добавить(ИмяНовогоПоля, Тип("ПолеФормы"), ЭтаФорма);
НовоеПоле.Вид = ВидПоляФормы.ПолеВвода;
НовоеПоле.ПутьКДанным = "Объект.ДополнительноеОписание";
КонецПроцедуры
Теперь в любой точке модуля формы мы можем мгновенно определить происхождение элемента с помощью простой текстовой проверки. Нам не нужно обращаться к тяжелым коллекциям или базе данных:
&НаКлиенте
Процедура ПроверитьЭлемент(Элемент)
// Используем функцию СтрНачинаетсяС для платформ 8.3.6 и выше
Если СтрНачинаетсяС(Элемент.Имя, "Прогр_") Тогда
ПоказатьПредупреждение(, "Этот элемент был создан программно!");
Иначе
ПоказатьПредупреждение(, "Это статический элемент из конфигуратора.");
КонецЕсли;
// Альтернативный вариант для более старых версий платформ:
// Если Лев(Элемент.Имя, 6) = "Прогр_" Тогда
КонецПроцедуры
Преимущества метода:
Если по каким-то причинам вы не можете влиять на имена программно добавляемых элементов (например, они генерируются внешней библиотекой или логикой БСП, которую нельзя менять), мы можем применить метод кэширования исходного состояния формы.
Для этого в самом начале обработчика ПриСозданииНаСервере, до того как начнет работать любой код по добавлению новых элементов, мы должны зафиксировать все существующие (статические) элементы.
Обычно разработчики предлагают сохранять этот список в реквизит формы с типом СписокЗначений, но это не оптимально с точки зрения производительности. Поиск в списке значений происходит линейно, что может замедлить форму при большом количестве элементов. Более того, хранить полноценные коллекции на клиенте накладно.
Мы применим более изящный способ: соберем имена статических элементов в одну текстовую строку с уникальным разделителем (например, точка с запятой) и сохраним ее в строковый реквизит формы. Поиск в такой строке работает очень быстро.
Выполним это по шагам:
Шаг 1. Создадим в конфигураторе вспомогательный реквизит формы СписокСтатическихЭлементов с типом Строка (длина неограничена, на форму выносить его не нужно).
Шаг 2. Заполним его в самом начале инициализации формы:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Шаг 2.1: Фиксируем все статические элементы из конфигуратора
СтрокаЭлементов = ";";
Для Каждого ЭлементИзКоллекции Из Элементы Цикл
СтрокаЭлементов = СтрокаЭлементов + ЭлементИзКоллекции.Имя + ";";
КонецЦикла;
СписокСтатическихЭлементов = СтрокаЭлементов;
// Получится строка вида: ";ГруппаШапка;Номер;Дата;Контрагент;Договор;"
// Шаг 2.2: Только ПОСЛЕ этого выполняем программное создание новых элементов
ДобавитьПрограммныеЭлементы();
КонецПроцедуры
Шаг 3. Напишем быструю функцию проверки:
&НаСервереБезКонтекста
Функция ЭтоПрограммныйЭлемент(ИмяЭлемента, СписокСтатическихЭлементов)
// Ищем имя элемента, обрамленное разделителями, чтобы избежать ложных срабатываний
// (например, чтобы имя "Код" не совпало с "Штрихкод")
ШаблонПоиска = ";" + ИмяЭлемента + ";";
Если СтрНайти(СписокСтатическихЭлементов, ШаблонПоиска) > 0 Тогда
Возврат Ложь; // Элемент присутствовал изначально в конфигураторе
Иначе
Возврат Истина; // Элемент был добавлен программно позже
КонецЕсли;
КонецФункции
Если вы разрабатываете расширение конфигурации и добавляете элементы формы в конфигураторе расширения, у вас может возникнуть вопрос: как платформа классифицирует такие элементы?
Проанализируем этот момент. Платформа 1С внедряет элементы из расширений в структуру формы до того, как вызывается обработчик ПриСозданииНаСервере модуля основной формы. Таким образом, если вы используете Решение 2 (кэширование списка в начале создания формы), элементы расширения благополучно попадут в категорию статических (из конфигуратора). С практической точки зрения это абсолютно верное поведение, так как они определены в метаданных интерфейса, пусть и в слое расширения, а не сгенерированы динамически кодом в процессе работы пользователя.
Не забывайте разделять понятия: элемент формы (визуальное представление на экране) и реквизит формы (данные, к которым элемент привязывается).
Часто при программном добавлении полей нам приходится программно создавать и реквизиты формы через метод ИзменитьРеквизиты(). Если вам нужно проверить происхождение именно реквизита, вы можете использовать метод формы ПолучитьРеквизиты(). Он возвращает массив объектов типа РеквизитФормы. Чтобы понять, был ли реквизит создан программно, сопоставьте его имя с метаданными основного объекта формы (например, документа или справочника):
&НаСервере
Функция РеквизитОбъектаСозданПрограммно(ИмяРеквизита)
// Проверяем, есть ли такой реквизит в реальных метаданных конфигурации
МетаданныеОбъекта = Метаданные.НайтиПоПолномуИмени("Документ.РеализацияТоваровУслуг");
Если МетаданныеОбъекта.Реквизиты.Найти(ИмяРеквизита) = Неопределено
И МетаданныеОбъекта.ТабличныеЧасти.Найти(ИмяРеквизита) = Неопределено Тогда
// Реквизита нет в метаданных объекта метаданных, значит он добавлен программно на форму
Возврат Истина;
КонецЕсли;
Возврат Ложь;
КонецФункции
Подведем итоги нашего анализа:
Элементы.Удалить() в блоках Попытка...Исключение ради проверки.ПриСозданииНаСервере (Решение 2).