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