В виду развития механизмов расширений, тема может быть немного холиварной, некоторые считают, что программное изменение форм больше не нужно, другие, наоборот, являются адептами программной модификации форм.

Я отношусь к адептам программной модификации форм и считаю это лучшим способом сократить количество анальной боли при обновлении конфигураций.

А ещё программная генерация элементов форм позволяет сделать, например, динамическое количество колонок (как в документах установки цен).

В моменте, делать изменения в визуальном интерфейсе намного проще, подтянул форму в расширение, поправил что нужно и радуешься. Но потом происходит обновление, и как то может начаться сильнейшая грусть от того, что разработчики поменяли названия групп, переименовали что-нибудь. А при попытке смерджить почти обязательно отвалится какая-нибудь очень важная фиговина.

Чтобы минимизировать последствия от обновления, формы нужно дорабатывать кодом.

Модификацию формы привязываем к событию «ПриСозданииНаСервере»

Группа формы


НовыйЭлемент = Элементы.Добавить("МОД_СвернутаяГруппа",Тип("ГруппаФормы"), Неопределено); // Неопределенно - означает добавление конец корневой группы формы
НовыйЭлемент.Вид = ВидГруппыФормы.ОбычнаяГруппа;
НовыйЭлемент.Заголовок = "Группа реквизитов объекта";
НовыйЭлемент.Отображение = ОтображениеОбычнойГруппы.Нет; 
НовыйЭлемент.ОтображениеУправления = ОтображениеУправленияОбычнойГруппы.Картинка;
НовыйЭлемент.Поведение = ПоведениеОбычнойГруппы.Свертываемая;
НовыйЭлемент.Группировка = ГруппировкаПодчиненныхЭлементовФормы.Вертикальная;

Так мы создадим группу на форме. Обратите внимание, что при создании нового элемента формы, мы не можем делать с ним сразу все операции. Например сделать группу сразу свернутой не можно через Элементы


Элементы.МОД_СвернутаяГруппа.Скрыть(); // Свернём созданную группу

Таким образом мы создали сразу свернутую группу. В которую в дальнейшем мы положим наши реквизиты объекта.

Программная модификация и генерация форм — иллюстрация

Реквизиты объекта

Начнем с самого простого. Мы создали реквизит в документе, справочнике, обработке или ещё где нибудь и хотим разместить его на форме, в надежде, что он будет сохраняться при записи нашего объекта.

Для примера возьмем реквизит Контрагент и разместим его в группе «МОД_СвернутаяГруппа»

Программная модификация и генерация форм — иллюстрация

НовыйЭлемент = Элементы.Добавить("МОД_КонтрагентВСвернутойГруппе", Тип("ПолеФормы"), Элементы.МОД_СвернутаяГруппа);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.АвтоОтметкаНезаполненного = Истина;
НовыйЭлемент.Заголовок = "Контрагент в свернутой группе";
НовыйЭлемент.ПутьКДанным = "Объект.Контрагент";
НовыйЭлемент.УстановитьДействие("ПриИзменении", "МОД_КонтрагентПриИзменении");
// МОД_НоменклатураПриИзменении - это клиентская процедура, которая будет обрабатывать событие при изменении
Программная модификация и генерация форм — иллюстрация

Табличная часть

Хотел бы отметить, что здесь идет речь о табличных частях. Про программную модификацию динамических списков есть отдельная статья.

Вот мы и добрались до табличной части. В принципе такая же самые принципы. Главное отличие в том, что нам сначала нужно вывести таблицу формы, а затем привязать к этой таблице колонки. Просто обращаем внимание на поле «ПутьКДанным».

Программная модификация и генерация форм — иллюстрация

// ТАБЛИЧНАЯ ЧАСТЬ
НовыйЭлемент = Элементы.Добавить("МОД_ГрафикПлатежей", Тип("ТаблицаФормы"), Элементы.МОД_СвернутаяГруппа);
НовыйЭлемент.ПутьКДанным = "Объект.ГрафикПлатежей";
НовыйЭлемент.Отображение = ОтображениеТаблицы.Список;
НовыйЭлемент.УстановитьДействие("Выбор", "МОД_ГрафикПлатежейВыбор");

// ДАТА
НовыйЭлемент = Элементы.Добавить("МОД_ГрафикПлатежейДата", Тип("ПолеФормы"), Элементы.МОД_ГрафикПлатежей);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.Заголовок = "Дата";
НовыйЭлемент.ПутьКДанным = "Объект.ГрафикПлатежей.Дата";
	
// СУММА
НовыйЭлемент = Элементы.Добавить("МОД_ГрафикПлатежейСумма", Тип("ПолеФормы"), Элементы.МОД_ГрафикПлатежей);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.Заголовок = "Сумма";
НовыйЭлемент.ПутьКДанным = "Объект.ГрафикПлатежей.Сумма";
НовыйЭлемент.УстановитьДействие("ПриИзменении", "МОД_ГрафикПлатежейСуммаПриИзменении");

Но табличная часть получилась уродской. Дата в одном конце таблицы, сумма в другом…

Программная модификация и генерация форм — иллюстрация

Для красоты выведем заглушку в нашу табличную часть


// ЗАГЛУШКА (для красивого вида таблицы)
МассивДобавляемыхРеквизитов = Новый Массив;
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы("МОД_ГрафикПлатежейЗаглушка", Новый ОписаниеТипов("Строка"), "Объект.ГрафикПлатежей", " "));
ИзменитьРеквизиты(МассивДобавляемыхРеквизитов);	

НовыйЭлемент = Элементы.Добавить("МОД_ГрафикПлатежейЗаглушка", Тип("ПолеФормы"), Элементы.МОД_ГрафикПлатежей);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.Доступность = Ложь;
НовыйЭлемент.ПутьКДанным = "Объект.ГрафикПлатежей.МОД_ГрафикПлатежейЗаглушка";
Программная модификация и генерация форм — иллюстрация

Красивее же?!

Реквизиты формы

При добавлении заглушки мы подобрались к программному созданию реквизитов формы. То есть тех реквизитов, которые мы не хотим сохранять автоматически в объекте (документе или справочнике). Но хотим выводить в них какие то служебные данные и не только.

Давайте сразу сделаем таблицу значений.

Я обычно подготавливаю необходимые типы данных, которые буду использовать в дальнейшем. Во первых они довольно длинные, во вторых они часто повторяющиеся.


// Описание квалификторов
КвалификаторЧислоНеотрицательное15_2 = Новый КвалификаторыЧисла(15, 2, ДопустимыйЗнак.Неотрицательный);
КвалификаторЧислоНеотрицательное10_3 = Новый КвалификаторыЧисла(10, 3, ДопустимыйЗнак.Неотрицательный);
КвалификаторСтрока100 = Новый КвалификаторыСтроки(100, ДопустимаяДлина.Переменная);
		
// Описание типов
ТипКонтрагентСсылка = Новый ОписаниеТипов("СправочникСсылка.Номенклатура");
ТипНоменклатураСсылка = Новый ОписаниеТипов("СправочникСсылка.Номенклатура");
ТипТаблицаЗначений = Новый ОписаниеТипов("ТаблицаЗначений");	
ТипЧислоНеотрицательное15_2 = Новый ОписаниеТипов("Число",,,КвалификаторЧислоНеотрицательное15_2); 
ТипЧислоНеотрицательное10_3 = Новый ОписаниеТипов("Число",,,КвалификаторЧислоНеотрицательное10_3);
ТипСтрока100 = Новый ОписаниеТипов("Строка",,,,КвалификаторСтрока100);	

Далее добавляем реквизиты на формы


МассивДобавляемыхРеквизитов = Новый Массив;

//Реквизит формы
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы("МОД_Контрагент", ТипКонтрагентСсылка,,"Контрагент"));
	
// Табличная часть
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы("МОД_ТаблицаТоваров", ТипТаблицаЗначений));

// Колонки табличной части
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы("Номенклатура", ТипНоменклатураСсылка, "МОД_ТаблицаТоваров"));
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы("Количество", ТипЧислоНеотрицательное10_3, "МОД_ТаблицаТоваров"));
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы("Цена", ТипЧислоНеотрицательное15_2, "МОД_ТаблицаТоваров"));
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы("Сумма", ТипЧислоНеотрицательное15_2, "МОД_ТаблицаТоваров"));
МассивДобавляемыхРеквизитов.Добавить(Новый РеквизитФормы("Комментарий", ТипСтрока100, "МОД_ТаблицаТоваров"));

ИзменитьРеквизиты(МассивДобавляемыхРеквизитов);

А теперь генерируем элементы


// 1. Добавим группу в которую будем складывать реквизиты формы
НовыйЭлемент = Элементы.Добавить("МОД_ГруппаТаблицаЗначений",Тип("ГруппаФормы"), Неопределено);
НовыйЭлемент.Вид = ВидГруппыФормы.ОбычнаяГруппа;
НовыйЭлемент.Заголовок = "Группа таблица значений";
НовыйЭлемент.Отображение = ОтображениеОбычнойГруппы.Нет; 
НовыйЭлемент.ОтображениеУправления = ОтображениеУправленияОбычнойГруппы.ГиперссылкаЗаголовка;
НовыйЭлемент.Поведение = ПоведениеОбычнойГруппы.Свертываемая;
НовыйЭлемент.Группировка = ГруппировкаПодчиненныхЭлементовФормы.Вертикальная;

// 2. Сделаем группу свернутой
Элементы.МОД_ГруппаТаблицаЗначений.Скрыть();
Элементы.МОД_ГруппаТаблицаЗначений.Группировка = ГруппировкаПодчиненныхЭлементовФормы.Вертикальная;

// 3. Выведем реквизит контрагент
НовыйЭлемент = Элементы.Добавить("МОД_Контрагент", Тип("ПолеФормы"), Элементы.МОД_ГруппаТаблицаЗначений);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.АвтоОтметкаНезаполненного = Истина;
НовыйЭлемент.Заголовок = "Контрагент";
НовыйЭлемент.ПутьКДанным = "МОД_Контрагент"; // имя реквизита формы
НовыйЭлемент.УстановитьДействие("ПриИзменении", "МОД_КонтрагентПриИзменении");


// 4. Выведем таблицу значений
НовыйЭлемент = Элементы.Добавить("МОД_ТаблицаТоваров", Тип("ТаблицаФормы"), Элементы.МОД_ГруппаТаблицаЗначений);
НовыйЭлемент.ПутьКДанным = "МОД_ТаблицаТоваров";
НовыйЭлемент.Отображение = ОтображениеТаблицы.Список;
НовыйЭлемент.УстановитьДействие("Выбор", "МОД_ТаблицаТоваровВыбор");

// 5. Выведем колонки

// НОМЕНКЛАТУРА
НовыйЭлемент = Элементы.Добавить("МОД_ТаблицаТоваровНоменклатура", Тип("ПолеФормы"), Элементы.МОД_ТаблицаТоваров);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.Заголовок = "Номенклатура";
НовыйЭлемент.ПутьКДанным = "МОД_ТаблицаТоваров.Номенклатура";

// КОЛИЧЕСТВО
НовыйЭлемент = Элементы.Добавить("МОД_ТаблицаТоваровКоличество", Тип("ПолеФормы"), Элементы.МОД_ТаблицаТоваров);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.Заголовок = "Кол-во";
НовыйЭлемент.ПутьКДанным = "МОД_ТаблицаТоваров.Количество";
НовыйЭлемент.УстановитьДействие("ПриИзменении", "МОД_КоличествоПриИзменении");

// ЦЕНА
НовыйЭлемент = Элементы.Добавить("МОД_ТаблицаТоваровЦена", Тип("ПолеФормы"), Элементы.МОД_ТаблицаТоваров);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.Заголовок = "Цена";
НовыйЭлемент.ПутьКДанным = "МОД_ТаблицаТоваров.Цена";
НовыйЭлемент.УстановитьДействие("ПриИзменении", "МОД_ЦенаПриИзменении");

// СУММА
НовыйЭлемент = Элементы.Добавить("МОД_ТаблицаТоваровСумма", Тип("ПолеФормы"), Элементы.МОД_ТаблицаТоваров);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.ТолькоПросмотр = Истина;
НовыйЭлемент.Заголовок = "Сумма";
НовыйЭлемент.ПутьКДанным = "МОД_ТаблицаТоваров.Сумма";

// КОММЕНТАРИЙ
НовыйЭлемент = Элементы.Добавить("МОД_ТаблицаТоваровКомментарий", Тип("ПолеФормы"), Элементы.МОД_ТаблицаТоваров);
НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
НовыйЭлемент.Заголовок = "Комментарий";
НовыйЭлемент.ПутьКДанным = "МОД_ТаблицаТоваров.Комментарий";
Программная модификация и генерация форм — иллюстрация

Ну вроде выглядит неплохо.

Кнопки

Добавление кнопки делается в 3 этапа

  1. Создание команды
  2. Размещение команды на форме
  3. Определение обработчика команды

// 1. Создание команды
НоваяКоманда = ЭтаФорма.Команды.Добавить("МОД_КомандаСказатьПривет");
НоваяКоманда.Заголовок = "Сказать привет!";
НоваяКоманда.Действие = "МОД_КомандаСказатьПривет"; //Имя клиентской процедуры

// 2. Размещение в командной панели
НовыйЭлемент = ЭтаФорма.Элементы.Добавить("КнопкаСказатьПривет", Тип("КнопкаФормы"), Элементы.ФормаКоманднаяПанель);
НовыйЭлемент.Заголовок = "Привет!";
НовыйЭлемент.ИмяКоманды = "МОД_КомандаСказатьПривет";

Обработчик команды — это просто клиентская процедура в модуле формы.


&НаКлиенте
Процедура МОД_КомандаСказатьПривет(Команда)
	
	Сообщение = Новый СообщениеПользователю;
	Сообщение.Текст = "Привет, программист!";
	Сообщение.Сообщить();
	
КонецПроцедуры
Программная модификация и генерация форм — иллюстрация

Меню и подменю

Кнопку мы добавляли в стандартную командную панель формы. Подобным образом можно размещать в командные панели динамического списка, табличной части итп. Но только вместо Элементы.ФормаКоманднаяПанель, было бы Элементы.ИмяТабличнойЧасти.КоманднаяПанель

Но давайте накодим свою командную панель с кнопками и подменю.


// Добавим в корень формы командную панель
НоваяКоманднаяПанель = Элементы.Добавить("МОД_КоманднаяПанельСПодменю", Тип("ГруппаФормы"), Неопределено);
НоваяКоманднаяПанель.Вид = ВидГруппыФормы.КоманднаяПанель;

// Разместим кнопку в командной панели 
НоваяКоманда = Команды.Добавить("МОД_КомандаСказатьХеллоу");
НоваяКоманда.Заголовок = "Хеллоу";
НоваяКоманда.Действие = "МОД_КомандаСказатьХеллоу"; //Имя клиентской процедуры

НовыйЭлемент = Элементы.Добавить("МОД_КнопкаСказатьХеллоу", Тип("КнопкаФормы"), Элементы.МОД_КоманднаяПанельСПодменю);
//НовыйЭлемент.Заголовок = "Хеллоу"; У кнопки не обязательно переопределять заголовок, если он уже установлен в заголовке команды
НовыйЭлемент.ИмяКоманды = "МОД_КомандаСказатьХеллоу";	

// Добавим подменю в командную панель
НовоеПодменю = Элементы.Добавить("МОД_ПодменюКоманднаяПанель",  Тип("ГруппаФормы"), Элементы.МОД_КоманднаяПанельСПодменю);
НовоеПодменю.Заголовок = "Это заголовок подменю";
НовоеПодменю.Вид = ВидГруппыФормы.Подменю;

// Разместим кнопку №1 в подменю
НоваяКоманда = Команды.Добавить("МОД_КомандаСказатьКоманднаяПанель");
НоваяКоманда.Заголовок = "Сказать командная панель";
НоваяКоманда.Действие = "МОД_КомандаСказатьКоманднаяПанель"; //Имя клиентской процедуры

НовыйЭлемент = Элементы.Добавить("МОД_КнопкаСказатьКоманднаяПанель", Тип("КнопкаФормы"), Элементы.МОД_ПодменюКоманднаяПанель);
НовыйЭлемент.Заголовок = "Сказать командная панель";
НовыйЭлемент.ИмяКоманды = "МОД_КомандаСказатьКоманднаяПанель";

// Разместим кнопку №2 в подменю
НоваяКоманда = Команды.Добавить("МОД_КомандаСказатьПока");
НоваяКоманда.Заголовок = "Сказать пока";
НоваяКоманда.Действие = "МОД_КомандаСказатьПока"; //Имя клиентской процедуры

НовыйЭлемент = Элементы.Добавить("МОД_КнопкаСказатьПока", Тип("КнопкаФормы"), Элементы.МОД_ПодменюКоманднаяПанель);
НовыйЭлемент.Заголовок = "Сказать пока";
НовыйЭлемент.ИмяКоманды = "МОД_КомандаСказатьПока";	

Получаем такой результат:

Программная модификация и генерация форм — иллюстрация

Как добавить элемент не в конец группы, а в середину

Всё это время, мы пользовались методом Элементы.Добавить(<Имя>, <ТипЭлемента>, <Родитель>), который добавляем новый элемент в конец родительской группы, если указан родитель и в конец формы, если в родителя передаем Неопределено.

Существует метод Элементы.Вставить(<Имя>, <ТипЭлемента>, <Родитель>, <Элемент>), где последним параметром передается элемент родительской группы, ПЕРЕД КОТОРЫМ мы хотим добавить наш вновь созданный элемент.

Представим, что мы хотим вставить новую кнопку между уже существующими

Программная модификация и генерация форм — иллюстрация

// Вставим кнопку №3 между кнопкой №1 и №2
НоваяКоманда = Команды.Добавить("МОД_КомандаНомерТри");
НоваяКоманда.Заголовок = "Сказать пока";
НоваяКоманда.Действие = "МОД_КомандаНомерТри"; //Имя клиентской процедуры

НовыйЭлемент = Элементы.Вставить("МОД_КнопкаМеждуПервойИВторой", Тип("КнопкаФормы"), Элементы.МОД_ПодменюКоманднаяПанель, Элементы.МОД_КнопкаСказатьПока);
НовыйЭлемент.Заголовок = "Кнопка между";
НовыйЭлемент.ИмяКоманды = "МОД_КомандаНомерТри";

Результат:

Программная модификация и генерация форм — иллюстрация

Всё тоже самое можно использовать для других элементов на форме.

Страницы

Это вообще easy peasy lemon squeezy


// 1. Создадим группу страницы, чтобы размещать на ней непосредственно страницы
СтраницыФормы = Элементы.Добавить("МОД_СтраницыФормы", Тип("ГруппаФормы"), Неопределено);
СтраницыФормы.Вид = ВидГруппыФормы.Страницы;

// 2. Страница 1
НоваяСтраница = Элементы.Добавить("МОД_Страница1", Тип("ГруппаФормы"), Элементы.МОД_СтраницыФормы);
НоваяСтраница.Вид = ВидГруппыФормы.Страница;
НоваяСтраница.Заголовок = "Страница 1";

// 3. Страница 1
НоваяСтраница = Элементы.Добавить("МОД_Страница2", Тип("ГруппаФормы"), Элементы.МОД_СтраницыФормы);
НоваяСтраница.Вид = ВидГруппыФормы.Страница;
НоваяСтраница.Заголовок = "Страница 2";

// 4. Разместим на страницах что нибудь, чтобы они в принципе отображались.
// пустые страницы на форме отображаться не будут
НоваяДекорация = Элементы.Добавить("МОД_ДекарацияСтраница1", Тип("ДекорацияФормы"), Элементы.МОД_Страница1);
НоваяДекорация.Заголовок = "Декарация страница 1";

НоваяДекорация = Элементы.Добавить("МОД_ДекарацияСтраница2", Тип("ДекорацияФормы"), Элементы.МОД_Страница2);
НоваяДекорация.Заголовок = "Декарация страница 2";
Программная модификация и генерация форм — иллюстрация

Перемещение элементов формы

А давайте теперь контрагента из группы с табличной значений переместим на страницу 2. И поставим его перед декорацией.


Элементы.Переместить(Элементы.МОД_Контрагент, Элементы.МОД_Страница2, Элементы.МОД_ДекарацияСтраница2);
Программная модификация и генерация форм — иллюстрация

Динамические списки

Про динамические списки у меня есть отдельная статья, посвященная только им.

Лайфхаки

Для некоторого упрощения можно использовать использовать библиотеки для упрощенной генерации элементов форм. Первая попавшаяся: https://infostart.ru/1c/articles/1270443/

Интересный инструмент — «Декомпилятор управляемых форм» https://infostart.ru/public/304736/ Поддерживает не все свойства элементов форм. Да и сгенерированный код получается немного «грязноватым», но бывает очень удобно накидать в конфигураторе элементов и в режиме предприятия просто сгенерировать значительную часть кода.

Файлы и заключение

Когда соберетесь модифицировать формы, то не пишите весь код в событии ПриСозданииНаСервере, а выносите блоками. Блок с табличной частью, блок с какой то одной группой итп. Я к такому привык очень быстро, когда при разработке приложений под ОС Android пересел с View на JetPack Compose. В котором вообще весь интерфейс генерируется кодом.

Подготовленный пример в виде внешней обработки, который работает на БП, РТ, УНФ, УТ, КА2, ERP