Как программно управлять динамическим списком формы: запрос, отборы, параметры и перечитывание

· 9 мин

Содержание

Чтобы программно обновить динамический список в 1С 8.3, на клиенте вызовите Элементы.Список.Обновить(). Если данные поменялись в другой форме или в серверной процедуре, удобнее оповестить все открытые списки сразу: ОповеститьОбИзменении(Ссылка) или ОповеститьОбИзменении(Тип("СправочникСсылка.Номенклатура")). Отборы, параметры и порядок меняются через свойства реквизита Список и его КомпоновщикНастроек, и после такой смены список перечитывается сам.

Это материал для разработчика: всё ниже относится к платформе 8.3 и управляемым формам, от конфигурации не зависит. Примеры кода написаны для реквизита формы с именем Список и одноимённой таблицы формы на нём.

Что внутри динамического списка и почему он «живёт» по правилам СКД

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

Под капотом у списка движок системы компоновки данных. Отсюда три следствия, которые определяют весь программный код:

  • 🧩 отбор, порядок и условное оформление — это объекты СКД: ОтборКомпоновкиДанных, ПорядокКомпоновкиДанных, УсловноеОформлениеКомпоновкиДанных;
  • 🔑 параметры запроса задаются как параметры компоновки, а не как у обычного объекта Запрос;
  • ⚙️ всеми настройками владеет КомпоновщикНастроек с тремя слоями: настройки разработчика, пользовательские и фиксированные.

Два режима: основная таблица и произвольный запрос

Список работает либо по основной таблице (ОсновнаяТаблица, например Справочник.Номенклатура), либо по своему запросу. Во втором случае включён флаг ПроизвольныйЗапрос, а текст лежит в ТекстЗапроса.

РежимКогда выбиратьЧто учесть
Основная таблицаОбычный список справочника или документаБыстрее всего, доступны все стандартные команды
Произвольный запрос с основной таблицейНужны поля из связанных таблиц, а строка списка остаётся объектомКлюч строки — ссылка основной таблицы, команды формы работают
Произвольный запрос без основной таблицыСвод, группировки, данные регистровНет стандартного открытия объекта, нужен ключ строки

Какие поля список реально читает

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

Это один из главных приёмов при работе со списком: поле, которое нужно коду, но не нужно пользователю, помечают «Использовать всегда». В конфигураторе это галочка в окне настройки полей списка, в коде — метод УстановитьОбязательноеИспользование:

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	Список.УстановитьОбязательноеИспользование("Склад", Истина);
	Список.УстановитьОбязательноеИспользование("Ответственный", Истина);
КонецПроцедуры

Как изменить состав полей и текст запроса из кода

Состав колонок динамического списка меняют двумя путями. Простой — переписать ТекстЗапроса и добавить на форму элемент под новое поле. Мягкий — оставить запрос как есть и просто скрыть или показать уже существующие колонки через Видимость.

Смена текста запроса на сервере

Делайте это в ПриСозданииНаСервере или в серверной процедуре, которую вызывает команда. Пример: в список номенклатуры добавляем цену из регистра сведений.

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

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

	Список.Параметры.УстановитьЗначениеПараметра("ДатаЦен", ТекущаяДатаСеанса());

	Если Элементы.Найти("СписокЦена") = Неопределено Тогда
		НовоеПоле = Элементы.Добавить("СписокЦена", Тип("ПолеФормы"), Элементы.Список);
		НовоеПоле.Вид = ВидПоляФормы.ПолеВвода;
		НовоеПоле.ПутьКДанным = "Список.Цена";
	КонецЕсли;

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

Имя регистра и его измерения здесь условные: подставьте те, что есть в вашей конфигурации. Принцип не меняется: новый текст запроса, значения всех параметров, затем элемент формы с ПутьКДанным = "Список.<ИмяПоля>".

После смены ТекстЗапроса пользовательские настройки списка (его отборы, сортировка, ширина колонок) могут не подойти к новому набору полей. Отборы по исчезнувшим полям перестанут работать. Меняйте запрос до того, как форма загрузит пользовательские настройки, то есть в ПриСозданииНаСервере.

Скрыть колонку без смены запроса

Если поле в запросе уже есть, гораздо дешевле управлять видимостью:

Элементы.СписокАртикул.Видимость = ПоказыватьАртикул;

Помните про правило из прошлого раздела: скрытое поле перестаёт читаться. Если код на него опирается, включите для него обязательное использование.

Отборы динамического списка из кода

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

ГдеВидит пользовательМожет снятьДля чего
Список.Отбор (фиксированные настройки)НетНетЖёсткое ограничение формы: «только документы этой организации»
Список.КомпоновщикНастроек.Настройки.ОтборДа, если режим отображения не «Недоступный»ДаОтбор по умолчанию, который можно поменять
Пользовательские настройкиДаДаОтборы, которые пользователь поставил сам и сохранил

Фиксированный отбор

&НаСервере
Процедура УстановитьОтборПоОрганизации(Организация)

	Список.Отбор.Элементы.Очистить();

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

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

Очистить() удаляет все фиксированные отборы, включая чужие. Если на форме их ставит не только ваш код, ищите свой элемент по ЛевоеЗначение и меняйте только его ПравоеЗначение и Использование.

💡 В конфигурациях на БСП (БП 3.0, ЗУП 3.1, УТ 11 и другие) есть готовая процедура ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка. Она сама находит элемент по имени поля, обновляет его или добавляет новый. Писать отбор вручную в таких базах незачем.

Группа «ИЛИ»

Нужно показать документы, где ответственный — текущий пользователь, или автор — текущий пользователь:

Группа = Список.Отбор.Элементы.Добавить(Тип("ГруппаЭлементовОтбораКомпоновкиДанных"));
Группа.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИли;
Группа.Использование = Истина;

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

Параметр сеанса ТекущийПользователь есть в типовых конфигурациях на БСП. В своей разработке подставьте свой способ получить пользователя.

Отбор, который видит пользователь

Чтобы отбор появился в панели быстрого отбора, добавьте его в КомпоновщикНастроек.Настройки.Отбор и задайте режим отображения РежимОтображенияЭлементаНастройкиКомпоновкиДанных.БыстрыйДоступ. Учтите: сохранённые пользовательские настройки при следующем открытии формы перекроют то, что вы поставили в коде.

Порядок строк

Сортировка задаётся так же, через объекты СКД:

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

Условное оформление строк

Выделить цветом строки, например непроведённые документы, тоже можно из кода. Оформление — ещё один объект СКД внутри списка:

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

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

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

Поле в условии должно читаться списком. Если колонки «Проведен» на форме нет, включите для неё обязательное использование, иначе оформление не сработает.

Параметры запроса динамического списка

Если в ТекстЗапроса есть &Параметр, значение ему задают через коллекцию Параметры списка. Метод тот же, что у компоновщика:

Список.Параметры.УстановитьЗначениеПараметра("ДатаЦен", Объект.Дата);
Список.Параметры.УстановитьЗначениеПараметра("Склады", МассивСкладов);

Несколько практических правил:

  1. Задавайте значения всех параметров в ПриСозданииНаСервере. Иначе первое чтение списка пойдёт с незаполненным параметром.
  2. Для условия В (&Склады) передавайте массив или список значений, а не одну ссылку, если складов может быть несколько.
  3. Смена значения параметра на клиенте, например в обработчике ПриИзменении поля отбора на форме, сразу перечитывает список. Отдельный вызов Обновить() не нужен.
  4. Если список строится по основной таблице без произвольного запроса, параметров у него нет. Ограничивайте выборку отбором.

Параметр или отбор: что выбрать

Отбор накладывается на итог запроса и виден в настройках компоновки. Параметр же работает внутри текста запроса: в условиях соединений, в виртуальных таблицах регистров (СрезПоследних(&Дата), Остатки(&Период)), в вычисляемых полях. Всё, что должно попасть внутрь виртуальной таблицы, — только параметром.

Как обновить динамический список программно, когда данные поменялись

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

Способ 1. Обновить() на таблице формы

Метод вызывают на клиенте после возврата из серверной процедуры:

&НаКлиенте
Процедура Перепровести(Команда)
	ВыделенныеСтроки = Элементы.Список.ВыделенныеСтроки;
	Если ВыделенныеСтроки.Количество() = 0 Тогда
		Возврат;
	КонецЕсли;
	ПерепровестиНаСервере(ВыделенныеСтроки);
	Элементы.Список.Обновить();
КонецПроцедуры

Обновляется только эта таблица формы. Другие открытые окна с тем же списком об изменениях не узнают.

Способ 2. ОповеститьОбИзменении

Глобальный метод, который сообщает всем динамическим спискам в сеансе пользователя: данные такого-то объекта или типа изменились. Каждый открытый список, где встречаются эти данные, перечитается сам.

// Одна ссылка
ОповеститьОбИзменении(ДокументСсылка);

// Все объекты типа
ОповеститьОбИзменении(Тип("ДокументСсылка.РеализацияТоваровУслуг"));

// Регистр сведений
ОповеститьОбИзменении(Тип("РегистрСведенийКлючЗаписи.ЦеныНоменклатуры"));

Метод клиентский. Из серверной процедуры его вызвать нельзя: верните с сервера массив изменённых ссылок и оповестите в цикле на клиенте. Изменений много — одного оповещения по типу хватит.

Способ 3. Оповестить и ОбработкаОповещения

Если перечитать нужно не просто список, а ещё и связанные данные формы (итоги в подвале, надпись с количеством), используйте пару Оповестить("ИмяСобытия", Параметр) и обработчик ОбработкаОповещения в нужной форме. Внутри обработчика проверьте имя события и вызовите Элементы.Список.Обновить().

СпособЧто обновляетКогда брать
Элементы.Список.Обновить()Одну таблицу в текущей формеКоманда этой же формы изменила данные
ОповеститьОбИзмененииВсе открытые списки с этими даннымиДанные поменяли в другой форме, обработке, при загрузке
Оповестить + ОбработкаОповещенияТо, что вы запрограммировали в обработчикеНужно перестроить ещё что-то кроме строк

Обновление по таймеру

Для списков, которые меняют другие пользователи (очередь заказов, журнал обмена), у динамического списка есть свойства автообновления и периода в секундах — их видно в палитре свойств реквизита. Если логику нужно контролировать самому, подключите обработчик ожидания:

&НаКлиенте
Процедура ПриОткрытии(Отказ)
	ПодключитьОбработчикОжидания("ОбновитьСписокПоТаймеру", 60);
КонецПроцедуры

&НаКлиенте
Процедура ОбновитьСписокПоТаймеру()
	Элементы.Список.Обновить();
КонецПроцедуры

Каждый такт — это запрос к базе от каждого открытого окна. Интервал в 5 секунд на списке с тяжёлым запросом и 30 пользователях заметно нагрузит сервер. Обычно хватает 30–60 секунд.

Частые ошибки при работе со списком из кода

Диагностика: выберите симптом

Ещё одна ловушка — вызвать Элементы.Список.Обновить() сразу после установки отбора или параметра. Список и так перечитается, а лишний вызов даст второй запрос к базе.

Частые вопросы

Можно ли изменить текст запроса динамического списка на клиенте?
Надёжнее делать это на сервере: в ПриСозданииНаСервере или в серверной процедуре команды. Там же добавляют элементы формы под новые поля.
Как программно получить все строки динамического списка, а не только видимые?
Никак: список читает данные порциями. Нужен полный набор — выполните тот же запрос с теми же отборами отдельным объектом Запрос на сервере.
Как обновить одну строку, а не весь список?
Вызовите ОповеститьОбИзменении с конкретной ссылкой. Платформа сама перечитает список, где эта ссылка встречается.
Сохранятся ли отборы, поставленные кодом, при следующем открытии формы?
Фиксированные (Список.Отбор) — нет, их ставит код при каждом создании формы. Отборы пользователя из панели быстрого отбора сохраняются в его настройках.
Почему после установки отбора пропадает текущая строка?
Строка не прошла новый отбор. Если её нужно сохранить, запомните ссылку до смены отбора и после верните через свойство ТекущаяСтрока таблицы формы.