Программное создание динамического списка или таблицы значений на управляемой форме 1С делается в два шага: сначала на сервере добавляются реквизиты формы методом ИзменитьРеквизиты, затем для них создаются элементы ТаблицаФормы и ПолеФормы через Элементы.Добавить. У таблицы значений каждая колонка — отдельный реквизит с путём к реквизиту-таблице. У динамического списка задаются текст запроса, основная таблица и параметры, а отборы, сортировка и условное оформление настраиваются через его собственные коллекции.

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

Когда формы меняют программно

Программная модификация формы оправдана, когда:

  • вы дорабатываете типовую форму и не хотите снимать её с поддержки или конфликтовать в расширении с визуальными правками;
  • состав колонок зависит от данных (например, по колонке на каждый вид цены);
  • одна и та же вставка нужна в нескольких формах — её удобно оформить общей процедурой.

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

  1. В ПриСозданииНаСервере добавить реквизиты (ИзменитьРеквизиты).
  2. Создать элементы и связать их с реквизитами через ПутьКДанным.
  3. Назначить обработчики событий через УстановитьДействие.
  4. Добавить условное оформление.
Программное создание динамического списка: реквизиты формы на сервере и связанные с ними элементы интерфейса
Программное создание динамического списка: реквизиты формы на сервере и связанные с ними элементы интерфейса

Имена программных реквизитов и элементов я начинаю с префикса (например, доп_), чтобы они не пересеклись с типовыми при обновлении.

Таблица значений на форме

Реквизиты: таблица и её колонки

1С
&НаСервере
Процедура ДобавитьРеквизитыТаблицыЦен()

	ИмяТаблицы = "доп_Цены";
	Реквизиты = Новый Массив;

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

	// Колонки: третий параметр — путь к реквизиту-таблице.
	Реквизиты.Добавить(Новый РеквизитФормы("Период",
		Новый ОписаниеТипов("Дата", , , Новый КвалификаторыДаты(ЧастиДаты.Дата)), ИмяТаблицы, "Период"));
	Реквизиты.Добавить(Новый РеквизитФормы("Номенклатура",
		Новый ОписаниеТипов("СправочникСсылка.Номенклатура"), ИмяТаблицы, "Номенклатура"));
	Реквизиты.Добавить(Новый РеквизитФормы("ВидЦены",
		Новый ОписаниеТипов("СправочникСсылка.ВидыЦен"), ИмяТаблицы, "Вид цены"));
	Реквизиты.Добавить(Новый РеквизитФормы("Цена",
		Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)), ИмяТаблицы, "Цена"));

	ИзменитьРеквизиты(Реквизиты);

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

Вызывайте ИзменитьРеквизиты один раз с полным массивом: каждый вызов пересоздаёт данные формы, и множественные вызовы заметно замедляют открытие.

Элементы: таблица формы и колонки

1С
// Выводит реквизит-коллекцию на форму в виде таблицы.
//
// Параметры:
//  Форма       - ФормаКлиентскогоПриложения
//  ИмяТаблицы  - Строка - имя реквизита-коллекции.
//  Родитель    - ГруппаФормы, ФормаКлиентскогоПриложения - куда поместить таблицу.
//  ИменаКолонок - Строка - имена колонок через запятую.
//  ТолькоПросмотр - Булево
//
&НаСервере
Процедура ВывестиТаблицуНаФорму(Форма, ИмяТаблицы, Родитель, ИменаКолонок, ТолькоПросмотр = Ложь) Экспорт

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

	Для Каждого ИмяКолонки Из СтрРазделить(ИменаКолонок, ",", Ложь) Цикл
		ИмяКолонки = СокрЛП(ИмяКолонки);
		Колонка = Форма.Элементы.Добавить(ИмяТаблицы + ИмяКолонки, Тип("ПолеФормы"), ТаблицаФормы);
		Колонка.ПутьКДанным = ИмяТаблицы + "." + ИмяКолонки;
		Колонка.Вид = ВидПоляФормы.ПолеВвода;
	КонецЦикла;

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

Имя колонки складывается из имени таблицы и имени поля (доп_ЦеныЦена) — по этому имени к колонке потом обращаются в условном оформлении.

Заполнение таблицы

С программным реквизитом удобнее работать как с обычной таблицей значений и затем выгружать её на форму:

1С
&НаСервере
Процедура ЗаполнитьТаблицуЦен(Номенклатура)

	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	Цены.Период КАК Период,
	|	Цены.Номенклатура КАК Номенклатура,
	|	Цены.ВидЦены КАК ВидЦены,
	|	Цены.Цена КАК Цена
	|ИЗ
	|	РегистрСведений.ЦеныНоменклатуры.СрезПоследних(, Номенклатура = &Номенклатура) КАК Цены";
	Запрос.УстановитьПараметр("Номенклатура", Номенклатура);

	ЗначениеВДанныеФормы(Запрос.Выполнить().Выгрузить(), ЭтотОбъект["доп_Цены"]);

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

К программному реквизиту обращайтесь через квадратные скобки: ЭтотОбъект["доп_Цены"]. Обращение через точку не пройдёт проверку модуля, если код лежит в общей процедуре, а не в модуле этой формы.

Условное оформление таблицы формы

Два правила: цена недоступна для редактирования, пока не включён флажок, и пустая цена подсвечивается.

1С
&НаСервере
Процедура ДобавитьОформлениеТаблицыЦен()

	// Цена недоступна, пока флажок «доп_РазрешитьИзменение» снят.
	Оформление = УсловноеОформление.Элементы.Добавить();
	Отбор = Оформление.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	Отбор.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("доп_РазрешитьИзменение");
	Отбор.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
	Отбор.ПравоеЗначение = Ложь;
	Оформление.Оформление.УстановитьЗначениеПараметра("ТолькоПросмотр", Истина);
	Оформление.Поля.Элементы.Добавить().Поле = Новый ПолеКомпоновкиДанных("доп_ЦеныЦена");

	// Пустая цена — розовый фон ячейки.
	Оформление = УсловноеОформление.Элементы.Добавить();
	Отбор = Оформление.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	Отбор.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("доп_Цены.Цена");
	Отбор.ВидСравнения = ВидСравненияКомпоновкиДанных.НеЗаполнено;
	Оформление.Оформление.УстановитьЗначениеПараметра("ЦветФона", WebЦвета.Розовый);
	Оформление.Поля.Элементы.Добавить().Поле = Новый ПолеКомпоновкиДанных("доп_ЦеныЦена");

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

Разница, о которую спотыкаются чаще всего: в отборе указывается путь к данным (доп_Цены.Цена), в полях оформления — имя элемента формы (доп_ЦеныЦена). Чтобы подсветить всю строку, добавьте в поля оформления все колонки.

Новые элементы отбора и оформления по умолчанию используются, явно ставить Использование = Истина не обязательно, но и не вредно.

Динамический список программно

Реквизит и запрос

1С
&НаСервере
Процедура ДобавитьДинамическийСписокЦен()

	Реквизиты = Новый Массив;
	Реквизиты.Добавить(Новый РеквизитФормы("доп_ИсторияЦен", Новый ОписаниеТипов("ДинамическийСписок")));
	ИзменитьРеквизиты(Реквизиты);

	Список = ЭтотОбъект["доп_ИсторияЦен"];
	Список.ПроизвольныйЗапрос = Истина;
	Список.ТекстЗапроса =
	"ВЫБРАТЬ
	|	Цены.Период КАК Период,
	|	Цены.Номенклатура КАК Номенклатура,
	|	Цены.ВидЦены КАК ВидЦены,
	|	Цены.Цена КАК Цена,
	|	Цены.Регистратор КАК Регистратор
	|ИЗ
	|	РегистрСведений.ЦеныНоменклатуры КАК Цены
	|ГДЕ
	|	Цены.Номенклатура = &Номенклатура";
	Список.ОсновнаяТаблица = "РегистрСведений.ЦеныНоменклатуры";

	// Параметр должен быть установлен до первого вывода списка.
	Список.Параметры.УстановитьЗначениеПараметра("Номенклатура", Объект.Ссылка);

	// Поле не выводится, но всегда доступно в данных строки.
	Список.УстановитьОбязательноеИспользование("Регистратор", Истина);

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

Если запрос не нужен, достаточно ПроизвольныйЗапрос = Ложь и ОсновнаяТаблица. Произвольный запрос даёт свободу, но лишает части оптимизаций платформы — используйте его, только когда нужны соединения или вычисляемые поля.

Отбор, сортировка и оформление списка

1С
&НаСервере
Процедура НастроитьДинамическийСписокЦен()

	Список = ЭтотОбъект["доп_ИсторияЦен"];

	// Отбор: цены начиная с начала года.
	Отбор = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	Отбор.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Период");
	Отбор.ВидСравнения = ВидСравненияКомпоновкиДанных.БольшеИлиРавно;
	Отбор.ПравоеЗначение = НачалоГода(ТекущаяДатаСеанса());

	// Сортировка: новые цены сверху.
	Сортировка = Список.Порядок.Элементы.Добавить(Тип("ЭлементПорядкаКомпоновкиДанных"));
	Сортировка.Поле = Новый ПолеКомпоновкиДанных("Период");
	Сортировка.ТипУпорядочивания = НаправлениеСортировкиКомпоновкиДанных.Убыв;

	// Оформление: цена от 4000 до 5000 — зелёный фон.
	Оформление = Список.УсловноеОформление.Элементы.Добавить();
	ГруппаИ = Оформление.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
	ГруппаИ.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИ;

	Условие = ГруппаИ.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	Условие.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Цена");
	Условие.ВидСравнения = ВидСравненияКомпоновкиДанных.БольшеИлиРавно;
	Условие.ПравоеЗначение = 4000;

	Условие = ГруппаИ.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	Условие.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Цена");
	Условие.ВидСравнения = ВидСравненияКомпоновкиДанных.Меньше;
	Условие.ПравоеЗначение = 5000;

	Оформление.Оформление.УстановитьЗначениеПараметра("ЦветФона", WebЦвета.СветлоЗеленый);
	// Поля оформления не заданы — оформление применится ко всей строке.

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

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

Вывод и обновление параметров

Динамический список выводится той же процедурой, что и таблица значений, — колонки создаются по полям запроса:

1С
	ВывестиТаблицуНаФорму(ЭтотОбъект, "доп_ИсторияЦен", Элементы.ГруппаЦены,
		"Период, ВидЦены, Цена", Истина);

Если параметр запроса зависит от реквизита формы, его нужно обновлять при изменении реквизита. Обработчик для программного элемента назначается через УстановитьДействие:

1С
// В ПриСозданииНаСервере после создания поля:
	Элементы.доп_ВидЦеныОтбор.УстановитьДействие("ПриИзменении", "Подключаемый_ВидЦеныПриИзменении");

&НаКлиенте
Процедура Подключаемый_ВидЦеныПриИзменении(Элемент)

	ЭтотОбъект["доп_ИсторияЦен"].Параметры.УстановитьЗначениеПараметра("ВидЦены", ЭтотОбъект["доп_ВидЦеныОтбор"]);

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

Установка параметра на клиенте сама обновит список — отдельный серверный вызов не нужен.

Где вызывать код и как убрать добавленное

Место вызова. Реквизиты и элементы создавайте в ПриСозданииНаСервере — это единственное событие, которое гарантированно выполняется один раз при открытии формы. Если состав колонок зависит от данных объекта (например, от выбранного вида цен), допускается повторная перестройка из серверной процедуры, вызванной с клиента, но тогда сначала удалите то, что создали раньше.

Удаление. Реквизиты удаляются тем же методом — вторым параметром передаётся массив путей:

1С
&НаСервере
Процедура УдалитьТаблицуЦен()

	Если Элементы.Найти("доп_Цены") <> Неопределено Тогда
		Элементы.Удалить(Элементы.доп_Цены);
	КонецЕсли;

	Удаляемые = Новый Массив;
	Удаляемые.Добавить("доп_Цены");
	ИзменитьРеквизиты(, Удаляемые);

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

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

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

Типовые формы. В типовых конфигурациях форму часто перестраивают и сами механизмы БСП (дополнительные реквизиты, подключаемые команды). Вызывайте свой код после типового, через аннотацию &После в расширении, и не полагайтесь на конкретные индексы элементов — ищите группы-родители по имени через Элементы.Найти.

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

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

Если нужно аккуратно доработать типовые формы без снятия с поддержки, это делается через расширения конфигурации; более крупные изменения — в рамках доработки 1С.

Коротко

  • Сначала реквизиты через ИзменитьРеквизиты, затем элементы через Элементы.Добавить с ПутьКДанным.
  • Колонки таблицы значений — реквизиты с путём к реквизиту-таблице.
  • Динамическому списку задайте запрос, основную таблицу и все параметры до вывода.
  • В условном оформлении формы: отбор по пути к данным, поля по именам элементов.
  • Обработчики программных элементов назначайте через УстановитьДействие.