Программное изменение формы 1С в расширении — это добавление полей, групп, страниц и команд кодом в обработчике ПриСозданииНаСервере заимствованной формы (с аннотацией &После), вместо перетаскивания элементов в редакторе формы расширения. Используются методы Элементы.Добавить / Элементы.Вставить, ИзменитьРеквизиты, Команды.Добавить и УстановитьДействие. Такой подход заметно проще переживает обновления типовой конфигурации: код добавляет элементы поверх любой версии формы.

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

Редактор формы в расширении или код

Классический путь: заимствовать справочник и форму в расширение, добавить реквизит объекту, перетащить его на форму, создать группу, страницу, команду. Это быстро, но у способа есть цена.

КритерийРедактор формы расширенияПрограммное изменение
Скорость первой доработкиБыстрееМедленнее
Видно ли элементы в конфигуратореДаНет, только в режиме «Предприятие»
Поведение при обновлении типовой формыНужно сравнение, возможны конфликты и «поехавшая» формаОбычно работает без изменений
Зависимость от структуры типовой формыПолнаяТолько от имён родительских групп
Перенос между конфигурациямиСложноОбщий модуль переносится как есть
Программное изменение формы 1С против правки в редакторе
Программное изменение формы 1С против правки в редакторе

На проектах я делаю так: если доработка формы — пара полей в типовой конфигурации, которая регулярно обновляется, — только кодом. Редактор формы расширения оставляю для собственных форм и для случаев, где программно получится нечитаемо (сложные таблицы, много условного оформления).

Подготовка расширения

  1. Создайте расширение, задайте префикс, например Расш_.
  2. Заимствуйте справочник Контрагенты и добавьте ему реквизит Расш_КонтактноеЛицо (строка). Для расширения, меняющего структуру данных, база выполнит реструктуризацию при применении; проверьте, что в свойствах расширения это допускается.
  3. Заимствуйте форму элемента справочника. Элементы в редакторе не трогаем.
  4. В модуле формы расширения создайте обработчик:
1С
&НаСервере
&После("ПриСозданииНаСервере")
Процедура Расш_ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)

	Расш_ДобавитьЭлементыКонтактногоЛица();

КонецПроцедуры

Общий модуль для создания элементов

Чтобы обработчик формы читался как описание, а не как стена кода, выношу создание элементов в серверный общий модуль расширения Расш_ЭлементыФормы.

1С
#Область ПрограммныйИнтерфейс

// Создаёт элемент формы. Если указан ЭлементПеред — вставляет перед ним.
Функция СоздатьЭлемент(Форма, Имя, ТипЭлемента, Родитель = Неопределено, ЭлементПеред = Неопределено) Экспорт

	Если Родитель = Неопределено Тогда
		Родитель = Форма;
	КонецЕсли;

	Если ЭлементПеред = Неопределено Тогда
		Возврат Форма.Элементы.Добавить(Имя, ТипЭлемента, Родитель);
	КонецЕсли;

	Возврат Форма.Элементы.Вставить(Имя, ТипЭлемента, Родитель, ЭлементПеред);

КонецФункции

// Устанавливает свойства элемента из структуры «ИмяСвойства — Значение».
Процедура УстановитьСвойства(Элемент, Свойства) Экспорт

	Если Свойства = Неопределено Тогда
		Возврат;
	КонецЕсли;
	ЗаполнитьЗначенияСвойств(Элемент, Свойства);

КонецПроцедуры

// Связывает события элемента с процедурами модуля формы:
// структура «ИмяСобытия — ИмяПроцедуры».
Процедура УстановитьСобытия(Элемент, События) Экспорт

	Если События = Неопределено Тогда
		Возврат;
	КонецЕсли;
	Для Каждого Событие Из События Цикл
		Элемент.УстановитьДействие(Событие.Ключ, Событие.Значение);
	КонецЦикла;

КонецПроцедуры

// Создаёт поле формы, связанное с данными.
Функция СоздатьПоле(Форма, Имя, ПутьКДанным, Родитель = Неопределено, Вид = Неопределено,
	ЭлементПеред = Неопределено, Свойства = Неопределено, События = Неопределено) Экспорт

	Поле = СоздатьЭлемент(Форма, Имя, Тип("ПолеФормы"), Родитель, ЭлементПеред);
	Поле.Вид = ?(Вид = Неопределено, ВидПоляФормы.ПолеВвода, Вид);
	Поле.ПутьКДанным = ПутьКДанным;

	УстановитьСвойства(Поле, Свойства);
	УстановитьСобытия(Поле, События);

	Возврат Поле;

КонецФункции

// Добавляет реквизит формы указанного типа.
Процедура СоздатьРеквизит(Форма, Имя, ОписаниеТипов, Заголовок = "") Экспорт

	Добавляемые = Новый Массив;
	Добавляемые.Добавить(Новый РеквизитФормы(Имя, ОписаниеТипов, , Заголовок));
	Форма.ИзменитьРеквизиты(Добавляемые);

КонецПроцедуры

// Создаёт обычную группу; по умолчанию — горизонтальную без рамки.
Функция СоздатьГруппу(Форма, Имя, Родитель = Неопределено, ЭлементПеред = Неопределено,
	Свойства = Неопределено) Экспорт

	Группа = СоздатьЭлемент(Форма, Имя, Тип("ГруппаФормы"), Родитель, ЭлементПеред);
	Группа.Вид = ВидГруппыФормы.ОбычнаяГруппа;
	Группа.Группировка = ГруппировкаПодчиненныхЭлементовФормы.ГоризонтальнаяВсегда;
	Группа.Отображение = ОтображениеОбычнойГруппы.Нет;
	Группа.ОтображатьЗаголовок = Ложь;

	УстановитьСвойства(Группа, Свойства);
	Возврат Группа;

КонецФункции

// Создаёт страницу в группе страниц.
Функция СоздатьСтраницу(Форма, Имя, ГруппаСтраниц, Заголовок, СтраницаПеред = Неопределено) Экспорт

	Страница = СоздатьЭлемент(Форма, Имя, Тип("ГруппаФормы"), ГруппаСтраниц, СтраницаПеред);
	Страница.Вид = ВидГруппыФормы.Страница;
	Страница.Заголовок = Заголовок;
	Страница.Группировка = ГруппировкаПодчиненныхЭлементовФормы.Вертикальная;

	Возврат Страница;

КонецФункции

// Создаёт команду формы и кнопку для неё.
Функция СоздатьКомандуИКнопку(Форма, ИмяКоманды, Действие, Заголовок, Родитель = Неопределено) Экспорт

	Команда = Форма.Команды.Добавить(ИмяКоманды);
	Команда.Действие = Действие;
	Команда.Заголовок = Заголовок;

	Кнопка = СоздатьЭлемент(Форма, "Кнопка" + ИмяКоманды, Тип("КнопкаФормы"), Родитель);
	Кнопка.ИмяКоманды = ИмяКоманды;

	Возврат Кнопка;

КонецФункции

#КонецОбласти

Обратите внимание на типы: на управляемой форме поле — это ПолеФормы с нужным Вид (ПолеВвода, ПолеФлажка, ПолеНадписи и т. д.), а не ПолеВвода, как в обычных формах.

Собираем доработку формы

Задача: на форме контрагента нужна новая страница «Контакты», на ней в одну строку — флажок «Показывать контактное лицо» и поле реквизита Расш_КонтактноеЛицо, ниже — кнопка, которая сообщает контактное лицо.

Новая страница на форме 1С, добавленная программно
Новая страница на форме 1С, добавленная программно
1С
&НаСервере
Процедура Расш_ДобавитьЭлементыКонтактногоЛица()

	// Реквизит формы для флажка видимости
	Расш_ЭлементыФормы.СоздатьРеквизит(ЭтотОбъект, "Расш_ПоказыватьКонтактноеЛицо",
		Новый ОписаниеТипов("Булево"), НСтр("ru = 'Показывать контактное лицо'"));
	Расш_ПоказыватьКонтактноеЛицо = Истина;

	// Страница — первой в группе страниц, если она есть на форме
	Родитель = ЭтотОбъект;
	ГруппаСтраниц = Элементы.Найти("ГруппаСтраницы");
	Если ГруппаСтраниц <> Неопределено И ГруппаСтраниц.ПодчиненныеЭлементы.Количество() > 0 Тогда
		Родитель = Расш_ЭлементыФормы.СоздатьСтраницу(ЭтотОбъект, "Расш_СтраницаКонтакты",
			ГруппаСтраниц, НСтр("ru = 'Контакты'"), ГруппаСтраниц.ПодчиненныеЭлементы[0]);
	КонецЕсли;

	// Горизонтальная группа: флажок + поле
	Группа = Расш_ЭлементыФормы.СоздатьГруппу(ЭтотОбъект, "Расш_ГруппаКонтактноеЛицо", Родитель);

	События = Новый Структура("ПриИзменении", "Расш_ПоказыватьКонтактноеЛицоПриИзменении");
	Расш_ЭлементыФормы.СоздатьПоле(ЭтотОбъект, "Расш_ПоказыватьКонтактноеЛицо",
		"Расш_ПоказыватьКонтактноеЛицо", Группа, ВидПоляФормы.ПолеФлажка, , , События);

	Расш_ЭлементыФормы.СоздатьПоле(ЭтотОбъект, "Расш_КонтактноеЛицо",
		"Объект.Расш_КонтактноеЛицо", Группа);

	// Команда и кнопка под группой
	Расш_ЭлементыФормы.СоздатьКомандуИКнопку(ЭтотОбъект, "Расш_СообщитьКонтактноеЛицо",
		"Расш_СообщитьКонтактноеЛицо", НСтр("ru = 'Сообщить контактное лицо'"), Родитель);

КонецПроцедуры

&НаКлиенте
Процедура Расш_ПоказыватьКонтактноеЛицоПриИзменении(Элемент)

	Элементы.Расш_КонтактноеЛицо.Видимость = Расш_ПоказыватьКонтактноеЛицо;

КонецПроцедуры

&НаКлиенте
Процедура Расш_СообщитьКонтактноеЛицо(Команда)

	ПоказатьПредупреждение(, СтрШаблон(НСтр("ru = 'Контактное лицо: %1'"), Объект.Расш_КонтактноеЛицо));

КонецПроцедуры

Имя группы страниц (ГруппаСтраницы в примере) посмотрите в своей типовой форме — оно отличается между конфигурациями. Поиск через Элементы.Найти с проверкой на Неопределено защищает от падения формы, если после обновления группу переименуют: элементы просто окажутся внизу формы.

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

Перемещение существующих элементов

Иногда нужно не создать, а переставить типовой элемент. Для этого есть Элементы.Переместить(Элемент, НовыйРодитель, ЭлементПеред). Например, перенести типовое поле «Комментарий» на новую страницу:

1С
ПолеКомментарий = Элементы.Найти("Комментарий");
Если ПолеКомментарий <> Неопределено Тогда
	Элементы.Переместить(ПолеКомментарий, Элементы.Расш_СтраницаКонтакты);
КонецЕсли;

Колонка в таблице формы и условное оформление

Тот же подход работает для табличных частей. Чтобы добавить колонку нового реквизита табличной части в таблицу формы, создайте поле с родителем-таблицей и путём к данным вида Объект.Товары.Расш_Партия:

1С
ТаблицаТовары = Элементы.Найти("Товары");
Если ТаблицаТовары <> Неопределено Тогда
	Расш_ЭлементыФормы.СоздатьПоле(ЭтотОбъект, "ТоварыРасш_Партия",
		"Объект.Товары.Расш_Партия", ТаблицаТовары);
КонецЕсли;

Условное оформление тоже задаётся кодом — через коллекцию УсловноеОформление формы. Например, выделить строки, где количество равно нулю:

1С
ЭлементОформления = УсловноеОформление.Элементы.Добавить();

ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("Товары");

Отбор = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
Отбор.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Объект.Товары.Количество");
Отбор.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
Отбор.ПравоеЗначение = 0;

ЭлементОформления.Оформление.УстановитьЗначениеПараметра("ЦветТекста", WebЦвета.Красный);

Обратите внимание: в поле оформления указывается имя элемента формы (таблицы или колонки), а в отборе — путь к данным. Перепутать их — самая частая причина того, что оформление «не срабатывает».

Скрытие и блокировка типовых элементов

Часто доработка сводится не к добавлению, а к ограничению: скрыть типовое поле или сделать его только для чтения для части пользователей. Это тоже делается в ПриСозданииНаСервере без правки макета формы:

1С
ПолеСкидка = Элементы.Найти("ПроцентСкидки");
Если ПолеСкидка <> Неопределено И Не РольДоступна("Расш_ИзменениеСкидок") Тогда
	ПолеСкидка.ТолькоПросмотр = Истина;
КонецЕсли;

Помните, что это только интерфейс: если реквизит важен для учёта, дополнительно проверяйте права в ПередЗаписью объекта или ограничивайте их ролями.

Типичные ошибки

  • Тип("ПолеВвода") на управляемой форме. Такого типа элемента там нет — нужен ПолеФормы с видом ВидПоляФормы.ПолеВвода.
  • Обработчик события не найден. УстановитьДействие ссылается на процедуру, которой нет в модуле формы расширения, или имя написано с ошибкой — ошибка всплывёт только при срабатывании события.
  • Конфликт имён. Элемент с таким именем уже есть в типовой форме или в другом расширении. Всегда используйте префикс.
  • Жёсткая ссылка на элементы типовой формы. Элементы.ГруппаСтраницы без проверки уронит форму, если группу переименуют. Используйте Элементы.Найти.
  • Реквизит формы не сохраняется между серверными вызовами. Реквизиты, добавленные через ИзменитьРеквизиты, живут как обычные реквизиты формы; а вот переменные модуля — нет. Состояние храните в реквизитах.
  • Условное оформление и видимость при открытии. Видимость, зависящая от данных, должна устанавливаться и в ПриСозданииНаСервере, а не только в ПриИзменении.

Доработки типовых форм для клиентов я по умолчанию делаю в расширениях и программно — это прямо сокращает время на обновление 1С: после выхода нового релиза достаточно проверить, что форма открывается и элементы на месте.

Итог

  • Программное изменение формы 1С выполняется в &После("ПриСозданииНаСервере") заимствованной формы расширения.
  • Поля создаются как ПолеФормы, группы и страницы — как ГруппаФормы, кнопки — как КнопкаФормы; реквизиты формы — через ИзменитьРеквизиты.
  • Небольшой общий модуль с функциями СоздатьПоле, СоздатьГруппу, СоздатьСтраницу, СоздатьКомандуИКнопку делает код формы коротким и переносимым.
  • Ищите типовые элементы через Элементы.Найти и используйте префикс — так доработка переживает обновления.
  • Редактор формы расширения оставьте для собственных форм и сложной вёрстки.