Как обновить динамический список из кода и не сбить выделение строк
Содержание
Динамический список на управляемой форме обновляют на клиенте командой Элементы.Список.Обновить(). Если данные поменялись в другой форме или в фоне, удобнее вызвать ОповеститьОбИзменении(Ссылка): платформа сама перечитает все открытые списки, где показан объект этого типа. Смена параметра запроса или отбора тоже перечитывает список, и отдельный вызов Обновить() после неё не нужен.
Частое заблуждение: пытаться обновить список на сервере или загрузить в него данные методом ЗагрузитьКолонку. У динамического списка нет строк в памяти формы, поэтому таких методов у него нет. Он каждый раз читает порцию записей из базы запросом. Всё, что можно сделать из кода, — сказать платформе «перечитай» или поменять условия чтения.
Как устроен динамический список и почему его не заполняют вручную
Реквизит формы типа ДинамическийСписок хранит не данные, а описание выборки: основную таблицу или текст запроса, параметры, отбор, сортировку, условное оформление. Строки на экране — это порция записей, которую платформа подгрузила под текущую прокрутку.
Отсюда три следствия для программиста:
- 📌 Добавить или удалить строку в самом списке нельзя. Меняйте данные в базе (записью объекта), а список перечитайте.
- 📌 Метода
Обновить()у реквизитаДинамическийСписокнет. Он есть у элемента формыТаблицаФормы, который этот реквизит показывает:Элементы.Список.Обновить(). - 📌 Обновление касается только текущего сеанса. Другой пользователь увидит изменения, когда его список перечитается сам или по его команде.
Сравнение с коллекциями, где строки живут в форме:
| Реквизит формы | Где хранятся строки | Как изменить содержимое |
|---|---|---|
ДинамическийСписок | В базе, на форму приходит порция | Изменить данные в базе, затем обновить элемент |
ТаблицаЗначений | В реквизите формы | Методы коллекции, ЗначениеВРеквизитФормы |
| Табличная часть объекта | В реквизите Объект | Методы табличной части, затем записать объект |
СписокЗначений | В реквизите формы | Добавить, ЗагрузитьЗначения, Очистить |
Если вам нужен список, который вы сами наполняете кодом (результат расчёта, подбор), берите реквизит ТаблицаЗначений, а не динамический список.
Метод Обновить() и другие способы обновить список программно
Вариантов четыре. Выбор зависит от того, где произошли изменения и сколько форм их должны увидеть.
| Способ | Где вызывается | Когда подходит |
|---|---|---|
Элементы.Список.Обновить() | Клиент, в той же форме | Данные изменили в этой форме: серверный вызов, групповая обработка |
ОповеститьОбИзменении(Ссылка или Тип) | Клиент, из любой формы | Объект записали в другой форме, нужно освежить все списки этого типа |
Оповестить(ИмяСобытия, Параметр, Источник) | Клиент, из любой формы | Нужно не просто перечитать, а что-то сделать: сменить отбор, спозиционироваться |
| Смена параметра, отбора, текста запроса | Клиент или сервер | Меняются условия выборки |
Обновление после серверной обработки
Типичный сценарий: пользователь выделил строки, нажал команду, сервер поменял документы. После возврата с сервера список нужно перечитать.
&НаКлиенте
Процедура ПометитьВыбранные(Команда)
Выделенные = Элементы.Список.ВыделенныеСтроки;
Если Выделенные.Количество() = 0 Тогда
Возврат;
КонецЕсли;
ПометитьНаСервере(Выделенные);
Элементы.Список.Обновить();
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ПометитьНаСервере(Знач Ссылки)
Для Каждого Ссылка Из Ссылки Цикл
Объект = Ссылка.ПолучитьОбъект();
Объект.Комментарий = "Проверено";
Объект.Записать();
КонецЦикла;
КонецПроцедуры
Вызывайте Обновить() один раз после всей обработки, а не внутри цикла. Каждое обновление — это новый запрос к базе.
Обновление из формы объекта
Пользователь записал элемент справочника в его форме, а список номенклатуры открыт рядом. Добавьте в форму объекта обработчик ПослеЗаписи:
&НаКлиенте
Процедура ПослеЗаписи(ПараметрыЗаписи)
ОповеститьОбИзменении(Объект.Ссылка);
КонецПроцедуры
Типовые формы объектов обычно уже вызывают такое оповещение при записи. Свой вызов нужен, когда данные поменял ваш код, а не стандартная запись формы: обработка, загрузка, запись регистра сведений.
Чтобы обновить все списки справочника разом, передайте тип:
ОповеститьОбИзменении(Тип("СправочникСсылка.Номенклатура"));
Списки регистров сведений
У записей регистра нет ссылки, ключ строки — это ключ записи. Поэтому для списка регистра передают тип ключа:
ОповеститьОбИзменении(Тип("РегистрСведенийКлючЗаписи.ЦеныНоменклатуры"));
Так пишут после того, как код перезаписал набор записей через РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей(). Стандартная форма записи регистра оповещает сама, а ваша обработка — нет. Имя регистра в примере условное: подставьте имя из своей конфигурации.
Если список построен произвольным запросом по нескольким таблицам, оповещение по типу может не дойти: платформа связывает список с основной таблицей. В таком случае надёжнее явный Оповестить с именем события.
Как сохранить выделение строк при обновлении списка
Обновить() сам по себе текущую строку не теряет: платформа запоминает ключ и после перечитывания возвращает курсор на ту же запись, если она осталась в выборке. Проблемы начинаются в трёх случаях:
- Записи после изменения выпали из отбора.
- Вы поменяли параметр или отбор, и выборка стала другой.
- Нужно восстановить множественное выделение, а не одну текущую строку.
Сохранить и вернуть множественное выделение
Скопируйте ссылки из ВыделенныеСтроки в обычный массив до изменений и верните их после:
&НаКлиенте
Процедура ОбновитьСохраняяВыделение()
Сохраненные = Новый Массив;
Для Каждого Ключ Из Элементы.Список.ВыделенныеСтроки Цикл
Сохраненные.Добавить(Ключ);
КонецЦикла;
Текущая = Элементы.Список.ТекущаяСтрока;
Элементы.Список.Обновить();
Элементы.Список.ВыделенныеСтроки.Очистить();
Для Каждого Ключ Из Сохраненные Цикл
Элементы.Список.ВыделенныеСтроки.Добавить(Ключ);
КонецЦикла;
Если Текущая <> Неопределено Тогда
Элементы.Список.ТекущаяСтрока = Текущая;
КонецЕсли;
КонецПроцедуры
Копия нужна потому, что ВыделенныеСтроки — живая коллекция элемента. Если перебирать её и одновременно очищать, вы потеряете то, что хотели сохранить.
Спозиционироваться на новой записи
После создания документа из кода удобно сразу поставить на него курсор:
Элементы.Список.ТекущаяСтрока = НовыйДокумент;
Платформа сама прокрутит список до этой записи. Если документ не попадает под отбор, курсор останется на месте без ошибки.
Для списка с произвольным запросом ключ строки зависит от свойства «Ключевое поле» и основной таблицы. Если ключевое поле не задано, ссылку из ТекущаяСтрока нельзя напрямую сравнивать со ссылкой на объект. Укажите основную таблицу или ключевое поле в свойствах списка.
Оповещения, события формы и перерисовка
Когда простого перечитывания мало, используйте пару Оповестить → ОбработкаОповещения. Источник шлёт событие с произвольным именем, а форма со списком решает, что с ним делать.
В форме-источнике:
Оповестить("ИзменилсяСклад", НовыйСклад, ЭтотОбъект);
В форме со списком:
&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
Если ИмяСобытия = "ИзменилсяСклад" Тогда
Список.Параметры.УстановитьЗначениеПараметра("Склад", Параметр);
КонецЕсли;
КонецПроцедуры
Здесь Обновить() не вызывается: установка параметра сама перечитывает список. Повторный вызов лишь удвоит запрос к базе.
Обновление после закрытия вспомогательной формы
Ещё один частый случай: из списка открывают обработку загрузки или форму группового изменения, а после её закрытия список должен показать результат. Оповещения здесь не нужны. Передайте в ОткрытьФорму описание оповещения о закрытии:
&НаКлиенте
Процедура ЗагрузитьИзФайла(Команда)
Оповещение = Новый ОписаниеОповещения("ПослеЗакрытияЗагрузки", ЭтотОбъект);
ОткрытьФорму("Обработка.ЗагрузкаНоменклатуры.Форма", , ЭтотОбъект, , , , Оповещение);
КонецПроцедуры
&НаКлиенте
Процедура ПослеЗакрытияЗагрузки(Результат, ДополнительныеПараметры) Экспорт
Если Результат = Истина Тогда
Элементы.Список.Обновить();
КонецЕсли;
КонецПроцедуры
Вспомогательная форма закрывается командой Закрыть(Истина), если загрузка прошла. При отмене она вызывает Закрыть() без параметра, Результат придёт как Неопределено, и лишнего перечитывания не будет.
Процедура-обработчик обязательно экспортная. Без слова Экспорт платформа не найдёт её и выдаст ошибку при закрытии формы.
Автообновление по таймеру
Если данные меняют другие пользователи или фоновые задания, есть два пути:
- ⏱ свойства элемента таблицы «Автообновление» и «Период автообновления» (в секундах) в редакторе формы;
- ⏱ обработчик ожидания, если нужна логика: обновлять только при открытой вкладке, менять период.
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ПодключитьОбработчикОжидания("ОбновитьСписокПоТаймеру", 60);
КонецПроцедуры
&НаКлиенте
Процедура ОбновитьСписокПоТаймеру()
Элементы.Список.Обновить();
КонецПроцедуры
Не ставьте период в несколько секунд для тяжёлого запроса на десятки пользователей. Каждый открытый экземпляр формы будет нагружать сервер. Для журналов документов разумный ориентир — 30–120 секунд.
Параметры, отбор и текст запроса из кода
Параметры и отбор — главный способ управлять списком программно. Их можно менять и на сервере, например в ПриСозданииНаСервере, где список ещё не показан.
Параметры запроса
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Список.Параметры.УстановитьЗначениеПараметра("Организация", Параметры.Организация);
Список.Параметры.УстановитьЗначениеПараметра("ДатаОтчета", ТекущаяДатаСеанса());
КонецПроцедуры
Значения всех параметров из текста запроса нужно задать до первого вывода формы. Иначе при открытии вы получите ошибку вида Не установлено значение параметра "Организация".
Отбор
В конфигурациях на БСП (БП 3.0, ЗУП 3.1, УТ 11 и другие) отбор ставят через общий модуль, чтобы не собирать элементы компоновки вручную:
ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(
Список, "Склад", Склад, ВидСравненияКомпоновкиДанных.Равно, , ЗначениеЗаполнено(Склад));
Последний аргумент включает отбор только при заполненном складе. Пустое значение снимет фильтр, а не отфильтрует строки с пустым складом.
Без БСП тот же отбор собирают через Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных")) и заполняют ЛевоеЗначение, ВидСравнения, ПравоеЗначение, Использование.
Текст запроса
Свойство Список.ТекстЗапроса можно переписать на сервере, если включён произвольный запрос (Список.ПроизвольныйЗапрос = Истина). Пригодится, когда форма открывается в разных режимах с разными колонками. После смены текста заново установите параметры: новый запрос не знает значений старого.
Частые ошибки при обновлении динамического списка
Элементы.Список.Обновить() попал в процедуру &НаСервере. Перенесите его в клиентскую процедуру после серверного вызова. На сервере меняйте параметры или отбор: список перечитается сам.Список.Обновить(), а не у элемента формы. Правильно: Элементы.Список.Обновить().ОповеститьОбИзменении с типом или ссылкой после записи.Обновить() вызван после установки параметров или в цикле. Уберите лишние вызовы: смена параметра уже перечитывает список.Продвинутые приёмы: ПриПолученииДанныхНаСервере и динамическое считывание
Доработка строк перед показом
Событие таблицы ПриПолученииДанныхНаСервере позволяет поменять значения и оформление уже выбранных строк. Удобно для вычисляемых колонок, которые сложно получить запросом: например, остаток из внешнего сервиса или пометка по сложному условию.
&НаСервереБезКонтекста
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
Для Каждого КлючИЗначение Из Строки Цикл
ДанныеСтроки = КлючИЗначение.Значение.Данные;
Если ДанныеСтроки.СуммаДокумента > 100000 Тогда
ДанныеСтроки.Комментарий = "Крупная сделка";
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Обработчик обязательно &НаСервереБезКонтекста и вызывается на каждую порцию строк. Держите его быстрым: никаких запросов в цикле, только один пакетный запрос по всем ключам порции.
Когда отключать динамическое считывание
Свойство «Динамическое считывание данных» заставляет список читать данные порциями. Для журнала на сотни тысяч записей это правильно. Для маленького справочника на 50 строк выключение свойства убирает лишние обращения при прокрутке, но список перечитывается целиком при каждом обновлении.
Приёмы, которые экономят нагрузку
- 🔧 Один
ОповеститьОбИзменении(Тип(...))после пакетной загрузки вместо оповещения по каждой ссылке. - 🔧 Условное оформление в настройках списка вместо раскраски в
ПриПолученииДанныхНаСервере, если хватает условия по полям. - 🔧 Отбор по индексированному полю. Медленный отбор делает медленным каждое обновление.
- 🔧 Проверка видимости формы перед обновлением по таймеру. Если пользователь держит открытыми пять журналов, перечитывать фоновые вкладки каждую минуту незачем: обновляйте только ту форму, с которой работают, а остальные — при активизации.
- 🔧 Минимум колонок в запросе. Поля, которые не выведены на форму и не нужны в коде, всё равно читаются из базы при каждом обновлении, если включены в произвольный запрос.
- 🔧 Отложенное обновление через
ПодключитьОбработчикОжидания(..., 0.1, Истина), если оповещения приходят пачкой: список перечитается один раз.
Частые вопросы
Можно ли использовать ЗагрузитьКолонку для динамического списка?
ЗагрузитьКолонку — метод таблицы значений и табличных частей. Если нужно массово поменять реквизит, загрузите значения в таблицу значений на сервере, запишите объекты, а затем обновите список.Как обновить список в другой открытой форме?
ОповеститьОбИзменении с типом объекта или Оповестить с именем события. Форма со списком перечитает его сама или обработает событие в ОбработкаОповещения.