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