Как заставить динамический список 1С показать свежие данные

· 9 мин

Содержание

Динамический список в 1С обновляют тремя способами: на клиенте вызывают Элементы.Список.Обновить(), после записи данных из кода вызывают ОповеститьОбИзменении(Ссылка), а при смене условий отбора меняют параметры или отбор самого списка, и он перечитывается сам. Выбор зависит от того, где изменились данные: в этой форме, в другой форме или на сервере.

Механизм общий для платформы 8.3 и управляемых форм, поэтому код одинаково работает в Бухгалтерии, ЗУП, УТ и в собственных разработках. Разница только в том, есть ли в конфигурации БСП с готовыми процедурами для отборов.

Как устроен динамический список и почему он не обновляется сам

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

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

  • 🔄 пользователь прокрутил список или нажал «Обновить» (клавиша F5 в форме списка);
  • 📝 объект записан из его стандартной формы — платформа сама оповещает открытые списки этого типа;
  • 📣 код вызвал ОповеститьОбИзменении() с подходящей ссылкой или типом;
  • ⚙️ изменились параметры, отбор, сортировка или условное оформление списка;
  • ⏱️ сработало автообновление, если оно включено у элемента формы.

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

Свойства, которые влияют на перечитывание

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

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

Программное обновление динамического списка: три рабочих приёма

Приём 1. Метод Обновить() у элемента формы

Метод принадлежит элементу формы «Таблица», а не реквизиту. Вызывать его можно только на клиенте.

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

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

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

&НаСервереБезКонтекста
Процедура ПометитьОбработаннымиНаСервере(Ссылки)
	Для Каждого Ссылка Из Ссылки Цикл
		Объект = Ссылка.ПолучитьОбъект();
		Объект.Обработан = Истина;
		Объект.Записать();
	КонецЦикла;
КонецПроцедуры

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

Приём 2. ОповеститьОбИзменении — обновить все открытые списки

Глобальный метод ОповеститьОбИзменении() сообщает всем открытым динамическим спискам, что данные определённого типа изменились. Каждый список, где есть строки этого типа, перечитается.

Параметр — ссылка, ключ записи регистра или тип:

&НаКлиенте
Процедура ПослеЗаписиНаСервере(Ссылка)
	// обновит все списки, где может быть этот элемент
	ОповеститьОбИзменении(Ссылка);
КонецПроцедуры

&НаКлиенте
Процедура ПослеГрупповойОбработки()
	// обновит все списки справочника «Номенклатура»
	ОповеститьОбИзменении(Тип("СправочникСсылка.Номенклатура"));
КонецПроцедуры

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

Приём 3. Оповестить + ОбработкаОповещения

Когда список нужно обновить по событию из другой формы, используют пару «оповещение — обработчик». Форма-источник отправляет сообщение:

&НаКлиенте
Процедура ПослеЗагрузкиЦен()
	Оповестить("ЗагруженыЦены", , ЭтотОбъект);
КонецПроцедуры

Форма со списком ловит его в обработчике ОбработкаОповещения:

&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
	Если ИмяСобытия = "ЗагруженыЦены" Тогда
		Элементы.Список.Обновить();
	КонецЕсли;
КонецПроцедуры

Этот приём нужен, когда изменились данные, которых в основной таблице списка нет. Например, список номенклатуры показывает цену из регистра сведений через соединение в запросе. Запись в регистр не изменит саму номенклатуру, и ОповеститьОбИзменении по типу номенклатуры формально не сработает. Явное событие решает задачу.

Обновление после закрытия вспомогательной формы

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

&НаКлиенте
Процедура ОткрытьЗагрузку(Команда)
	Оповещение = Новый ОписаниеОповещения("ПослеЗакрытияЗагрузки", ЭтотОбъект);
	ОткрытьФорму("Обработка.ЗагрузкаЦен.Форма", , ЭтотОбъект, , , , Оповещение);
КонецПроцедуры

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

Обработка закрывается вызовом Закрыть(Истина), если что-то загрузила. При закрытии крестиком придёт Неопределено, и лишнего обновления не будет.

Обновление после фонового задания

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

В конфигурациях на БСП задание запускают через ДлительныеОперации.ВыполнитьВФоне, а на клиенте ждут результат через ДлительныеОперацииКлиент.ОжидатьЗавершение с описанием оповещения. В процедуре, которую вызовет это оповещение, и ставят ОповеститьОбИзменении(Тип(...)) или Элементы.Список.Обновить().

Без БСП логика та же: клиентский обработчик ожидания опрашивает состояние задания, и после статуса «завершено» обновляет список. Обновлять список на каждом такте опроса не нужно — данные всё равно ещё не дописаны.

Какой способ выбрать

СитуацияСпособМинус
Данные изменились в этой же формеЭлементы.Список.Обновить()не трогает другие окна
Записан объект или запись регистра из кодаОповеститьОбИзменении(Ссылка)только клиент
Массовая обработка одного типаОповеститьОбИзменении(Тип(...))перечитываются все списки типа
Изменилась связанная таблица из запросаОповестить + ОбработкаОповещенияимя события надо согласовать вручную
Данные меняются извне без участия пользователяавтообновлениелишняя нагрузка при коротком периоде

Параметры и отборы: как обновлять список при смене условий

Изменение параметра или отбора само запускает перечитывание. Вызывать Обновить() после этого не нужно — получится двойной запрос.

Параметры произвольного запроса

Если в запросе списка есть параметр &Дата или &Организация, его значение задают через Параметры реквизита:

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	Список.Параметры.УстановитьЗначениеПараметра("Дата", ТекущаяДатаСеанса());
	Список.Параметры.УстановитьЗначениеПараметра("Организация", Справочники.Организации.ПустаяСсылка());
КонецПроцедуры

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

Задайте значение каждого параметра запроса уже при создании формы. Если хотя бы один останется без значения, список не откроется и покажет ошибку о незаполненном параметре.

Отбор через компоновку данных

Отбор динамического списка — это объект Отбор системы компоновки данных. Добавить элемент можно так:

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

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

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

Параметр или отбор — что лучше

  • ⚡ Параметр подставляется прямо в текст запроса. Его удобно ставить внутрь соединений и вложенных таблиц, где отбор не дотянется.
  • 🎛️ Отбор накладывается поверх результата и виден пользователю в настройках списка. Пользователь может его снять, если отбор не зафиксирован как недоступный.
  • 🔒 Для жёсткого ограничения (например, только свой склад) берите параметр или отбор в фиксированных настройках, а не пользовательский.

Оптимизация производительности при частых обновлениях списка

Медленный список чаще всего тормозит не из-за метода обновления, а из-за того, сколько раз и какой запрос выполняется.

Не обновляйте дважды

  • 🚫 Обновить() сразу после смены параметра или отбора — лишний запрос.
  • 🚫 Обновить() после ОповеститьОбИзменении в той же форме — список уже перечитается по оповещению.
  • 🚫 Оповещение в цикле по каждой ссылке. Для массовой операции передайте тип один раз.

Автообновление без перегрузки сервера

Автообновление включают в свойствах элемента формы «Таблица»: флаг «Автообновление» и «Период автообновления» в секундах. Из кода:

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	Элементы.Список.Автообновление = Истина;
	Элементы.Список.ПериодАвтоОбновления = 60;
КонецПроцедуры

Каждый такт — серверный вызов от каждого пользователя с открытой формой. Двадцать пользователей с периодом 5 секунд дают 240 запросов в минуту только на один список. Для журнала заказов, где задержка в минуту некритична, 60 секунд — разумный старт.

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

&НаКлиенте
Процедура ПриОткрытии(Отказ)
	ПодключитьОбработчикОжидания("ПроверитьИзменения", 30, Ложь);
КонецПроцедуры

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

ПроверитьИзмененияНаСервере — ваша серверная функция без контекста. Она выполняет лёгкий запрос ВЫБРАТЬ ПЕРВЫЕ 1 по дате изменения записей, а возвращает структуру с признаком ЕстьНовые и текущим временем сервера Момент из ТекущаяДатаСеанса(). Такой запрос дешевле полного перечитывания списка, а время берётся с сервера, поэтому расхождение часов на компьютерах пользователей не мешает. МоментПроверки — реквизит формы типа «Дата».

Сам запрос списка

  • 📌 Указывайте основную таблицу, даже при произвольном запросе. Без неё платформа хуже позиционирует строки и медленнее листает.
  • 🧮 Избегайте вложенных запросов и группировок в тексте списка: они ломают порционное чтение, платформе приходится выбирать всё.
  • 🗂️ Поля, по которым ставят отбор и сортировку, должны быть индексированы — иначе каждое обновление будет сканировать таблицу целиком.
  • 🧩 Дополнительные данные по строкам удобнее подтягивать в событии ПриПолученииДанныхНаСервере одним запросом по всем ключам порции, а не запросом на каждую строку.

Обработка ошибок при обновлении динамического списка

Часть проблем проявляется явной ошибкой, часть — тем, что список просто молча показывает старые строки.

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

Сохранение позиции после обновления

Пользователь отредактировал строку в середине длинного списка, а после обновления оказался вверху — частая жалоба. Курсор держится сам, если основная таблица задана и строка не исчезла из-под отбора. Если позицию нужно выставить явно:

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

Для списков с основной таблицей значение ТекущаяСтрока — ссылка. Для регистров сведений — ключ записи.

Частые ошибки разработчика

  • 🔁 Обновление в обработчике ПриАктивизацииСтроки. Активизация срабатывает при каждом перемещении курсора, и список начинает перечитываться бесконечно.
  • 🧱 Отключённое динамическое считывание «для надёжности». На малой базе незаметно, на рабочей — форма открывается десятки секунд.
  • 🧹 Очистка отборов при каждом обновлении. Пользовательский отбор, который человек поставил вручную, пропадает.
  • 🗃️ Хранение выборки в реквизите формы. Если нужен «снимок» данных, это таблица значений, а не динамический список. Динамический список для этого не предназначен: он всегда читает текущее состояние базы.
  • 🎯 Оповещение не той ссылкой. Записали строку регистра, а оповещают ссылкой документа-регистратора. Список регистра не перечитается.

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

Можно ли обновить динамический список на сервере?
Метода Обновить() на сервере нет. Но если в серверной процедуре изменить параметр или отбор реквизита Список, после возврата на клиент список перечитается сам.
Почему список в другой открытой форме не обновился после записи?
Обновить() действует только на свою форму. Чтобы обновились все окна, вызовите ОповеститьОбИзменении со ссылкой или типом объекта.
Нужно ли обновлять список после проведения документа из формы документа?
Нет. Стандартная форма объекта после записи сама оповещает открытые списки, и журнал перечитывается без кода.
Как обновить список, который открыт у других пользователей?
Оповещение работает только в сеансе текущего пользователя. У остальных поможет автообновление элемента формы или обработчик ожидания с проверкой изменений.
Сбрасывает ли обновление пользовательские настройки списка?
Нет. Отборы, сортировка и группировки, которые пользователь задал сам, сохраняются, если код не очищает их явно.