Разработка и Конфигурация · 15 мин
Как обойти строки динамического списка в 1С
Как программно обойти строки динамического списка 1С: получение данных через СКД, обход таблицы значений, ограничения и типичные ошибки с примерами кода.
Динамический список в 1С — это объект формы, который отображает результат запроса и автоматически обновляется при изменении данных. Главная особенность, которая ставит в тупик начинающих разработчиков: у динамического списка нет прямого доступа к строкам, как у таблицы значений. Нельзя написать цикл вида «Для Каждого Строка Из Список Цикл» — такой код просто не скомпилируется.
Причина в архитектуре: динамический список хранит не сами данные, а настройки запроса и отображает записи порциями по мере прокрутки. Платформа загружает в память только видимую часть, поэтому полного набора строк на клиенте физически не существует. Чтобы обойти все строки, нужно получить данные другим способом — через схему компоновки данных, через временную таблицу значений или через выборку запроса.
Ниже разобраны рабочие подходы: от простого обхода выделенных строк до полного получения всех данных списка с учётом отборов, установленных пользователем. Каждый способ имеет свои ограничения по производительности и объёму данных, поэтому важно выбрать подходящий под конкретную задачу.
Как устроен динамический список и почему его нельзя обойти напрямую
Динамический список — это реквизит формы типа «ДинамическийСписок», который содержит текст запроса, настройки отборов, сортировки и группировки, но не сами записи. Когда пользователь открывает форму списка, платформа выполняет запрос и показывает первую порцию строк. При прокрутке подгружаются следующие порции. Такой механизм позволяет работать с таблицами, содержащими миллионы записей, без падения производительности.
Из этого следует ключевое ограничение: в любой момент времени на клиенте доступен лишь фрагмент данных. Даже если бы платформа позволяла перебрать «текущие» строки, вы получили бы только видимую часть, а не полный результат запроса. Поэтому любая задача обхода сводится к тому, чтобы выполнить запрос списка самостоятельно и обойти его результат.
Также важно различать две разные задачи: обойти строки, которые пользователь выделил мышью, и обойти все записи, соответствующие текущим отборам списка. Это принципиально разные сценарии с разными решениями, и путать их — распространённая ошибка.
Обход выделенных строк через свойство ВыделенныеСтроки
Самая простая задача — обработать строки, которые пользователь выделил в списке. Для этого у элемента формы «Таблица», связанного с динамическим списком, есть свойство ВыделенныеСтроки. Оно содержит массив идентификаторов строк, по которым можно получить данные через метод ДанныеСтроки.
// Клиентская процедура обработки выделенных строк
&НаКлиенте
Процедура ОбработатьВыделенные(Команда)
Для Каждого ИдСтроки Из Элементы.Список.ВыделенныеСтроки Цикл
ДанныеСтроки = Элементы.Список.ДанныеСтроки(ИдСтроки);
Если ДанныеСтроки <> Неопределено Тогда
Сообщить("Ссылка: " + ДанныеСтроки.Ссылка);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Метод ДанныеСтроки возвращает структуру с полями строки или Неопределено, если строка недоступна (например, список успел обновиться). Всегда проверяйте результат на Неопределено — это защитит от ошибок при параллельном изменении данных другими пользователями.
Если нужны только ссылки на объекты, а не все поля строки, можно работать с идентификаторами напрямую: для списка ссылочного типа идентификатор выделенной строки часто совпадает со ссылкой. Однако надёжнее получать данные через ДанныеСтроки, потому что состав полей и тип идентификатора зависят от настроек основной таблицы списка.
Получение всех данных списка через СКД
Когда нужно обойти все записи, которые соответствуют текущим отборам динамического списка, правильный инструмент — схема компоновки данных. Динамический список внутри себя использует механизм СКД, и его результат можно получить программно: построить компоновщик макета, вывести результат в таблицу значений и обойти её обычным циклом.
&НаСервере
Функция ПолучитьДанныеСписка()
// Получаем СКД из динамического списка
СхемаКомпоновки = Элементы.Список.ПолучитьИсполняемуюСхемуКомпоновкиДанных();
Настройки = Элементы.Список.ПолучитьИсполняемыеНастройкиКомпоновкиДанных();
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновки, Настройки, , ,
Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);
Результат = Новый ТаблицаЗначений;
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
ПроцессорВывода.УстановитьОбъект(Результат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
Возврат Результат;
КонецФункции
Главное достоинство этого подхода — методы ПолучитьИсполняемуюСхемуКомпоновкиДанных и ПолучитьИсполняемыеНастройкиКомпоновкиДанных возвращают схему и настройки с учётом всех отборов, которые пользователь установил интерактивно. То есть вы обходите ровно те данные, которые видит пользователь, включая пользовательские отборы, поиск и сортировку.
Недостаток — объём памяти. Весь результат запроса загружается в таблицу значений на сервере. Если под отбор попадают сотни тысяч записей, это может привести к заметному потреблению памяти и тайм-ауту длительных операций. Для больших объёмов лучше использовать обход выборки запроса без выгрузки в таблицу целиком.
Обход через самостоятельный запрос к данным
Альтернативный путь — взять текст запроса динамического списка и выполнить его самостоятельно, добавив нужные условия. Текст запроса доступен через свойство ТекстЗапроса динамического списка (если установлен произвольный запрос) или формируется платформой автоматически по основной таблице.
&НаСервере
Процедура ОбойтиДанныеЗапросом()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ПометкаУдаления";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Обработка каждой записи
КонецЦикла;
КонецПроцедуры
Обход выборки методом Следующий() экономит память: записи читаются из СУБД последовательно, а не загружаются целиком. Это предпочтительный вариант для пакетной обработки больших объёмов — например, при массовом изменении реквизитов или формировании документов по данным списка.
Слабое место подхода — отборы пользователя придётся воспроизводить вручную. Текст запроса списка не содержит интерактивных отборов, они применяются механизмом СКД поверх запроса. Если важно учесть именно пользовательские настройки, комбинируйте подходы: получайте исполняемые настройки СКД, а данные читайте выборкой.
Сравнение вариантов
Подходит для обработки строк, отмеченных пользователем вручную. Ограничение: работает только на клиенте и только с видимыми данными.
Учитывает все пользовательские отборы и настройки. Ограничение: весь результат загружается в память сервера.
Экономит память при больших объёмах, подходит для пакетной обработки. Ограничение: пользовательские отборы нужно воспроизводить вручную.
Пошаговый сценарий: массовая обработка записей из списка
Типовая прикладная задача — добавить на форму списка кнопку, которая выполняет действие над всеми записями по текущему отбору: например, установить признак, перепровести документы или сформировать печатные формы. Разберём последовательность на примере списка документов.
Сначала создайте команду формы и разместите кнопку в командной панели таблицы списка. В обработчике команды на клиенте покажите вопрос пользователю с подтверждением — массовые операции необратимы, и случайный запуск по всему списку без отборов может затронуть тысячи объектов. После подтверждения вызовите серверную процедуру.
На сервере получите данные через СКД (как в примере выше), затем обойдите таблицу значений в цикле. Для каждой строки получайте объект по ссылке, изменяйте и записывайте. Обязательно оборачивайте обработку каждой записи в попытку-исключение: одна проблемная запись не должна прерывать обработку остальных. Ошибки собирайте в массив и показывайте пользователю по завершении.
- 1Добавьте команду формы и кнопку в командную панель таблицы списка
- 2В клиентском обработчике запросите подтверждение у пользователя
- 3На сервере получите исполняемую СКД и настройки динамического списка
- 4Выведите результат компоновки в таблицу значений
- 5Обойдите таблицу циклом, обрабатывая каждую запись в попытке-исключении
- 6Сообщите пользователю количество обработанных записей и список ошибок
&НаСервере
Процедура ОбработатьЗаписиНаСервере()
ДанныеСписка = ПолучитьДанныеСписка(); // функция из раздела про СКД
Ошибки = 0;
Для Каждого Строка Из ДанныеСписка Цикл
Попытка
Объект = Строка.Ссылка.ПолучитьОбъект();
Объект.Комментарий = "Обработан пакетно";
Объект.Записать();
Исключение
Ошибки = Ошибки + 1;
КонецПопытки;
КонецЦикла;
КонецПроцедуры
После завершения обработки обновите список на клиенте методом Элементы.Список.Обновить(), чтобы пользователь увидел актуальное состояние данных. Без обновления список покажет старые значения до следующего автоматического обновления.
Типичные ошибки при обходе динамического списка
Первая и самая частая ошибка — попытка обратиться к строкам списка как к коллекции: Список.Количество(), Список[0] и подобные конструкции. Динамический список не является коллекцией значений, и такой код вызовет ошибку исполнения. Запомните: данные получают через СКД, запрос или ДанныеСтроки, но не через индексацию списка.
Вторая ошибка — обход данных на клиенте. Методы получения исполняемой СКД и вывода в таблицу значений работают только на сервере. Если написать этот код в клиентской процедуре без директивы компиляции, получите ошибку о недоступности типов «КомпоновщикМакетаКомпоновкиДанных» на тонком клиенте.
Третья ошибка — игнорирование отборов. Разработчик выполняет запрос вручную, забывая, что пользователь установил отбор по организации или периоду, и обрабатывает лишние записи. Последствия могут быть серьёзными: массовое изменение чужих документов. Всегда либо используйте исполняемые настройки СКД, либо явно переносите отборы в свой запрос.
Как проверить результат и выбрать подходящий метод
После реализации обхода проверьте три вещи. Во-первых, количество обработанных записей должно совпадать с количеством строк, которые видит пользователь при тех же отборах (его можно прикинуть по счётчику в настройках списка или отдельным запросом с КОЛИЧЕСТВО). Во-вторых, убедитесь, что при пустом результате код не падает, а корректно сообщает об отсутствии данных. В-третьих, проверьте работу под пользователем с ограниченными правами: RLS может изменить состав доступных записей, и обход должен учитывать это автоматически, если выполняется в привилегированном режиме — осознанно.
Как проверить результат
Совпадает с количеством строк по текущим отборам списка
Обработано больше записей, чем видно в списке — отборы не учтены
Пользователь получает понятное сообщение об отсутствии данных
Ошибка исполнения или молчаливое завершение без результата
Список показывает актуальные данные сразу после операции
Пользователь видит устаревшие значения до ручного обновления
Выбор метода зависит от задачи. Для обработки выделенного — только ВыделенныеСтроки. Для обхода всего видимого результата с учётом отборов при умеренном объёме — СКД с выводом в таблицу значений. Для пакетной обработки больших объёмов — самостоятельный запрос с выборкой, при необходимости с порционной обработкой через индексацию и условия по ключу.
Как выбрать действие
FAQ: частые вопросы об обходе динамического списка
Можно ли узнать общее количество строк динамического списка?
Штатного свойства «количество строк» у динамического списка нет, поскольку данные загружаются порциями. Чтобы узнать количество, выполните отдельный запрос с функцией КОЛИЧЕСТВО по той же таблице и условиям, либо получите результат через СКД и посчитайте строки таблицы значений. Учитывайте, что подсчёт на больших таблицах — ресурсоёмкая операция.
Почему ДанныеСтроки возвращает Неопределено для выделенной строки?
Это происходит, если список успел обновиться между выделением и обращением к данным, либо если строка ушла из загруженной порции. Всегда проверяйте результат на Неопределено. Если нужна гарантированная работа со ссылкой, сохраняйте ссылки в отдельный массив сразу при получении выделенных строк.
Как обойти строки динамического списка с группировками?
При выводе результата СКД в таблицу значений группировки «разворачиваются» в плоскую структуру с детальными записями. Если нужна иерархия, выводите результат в дерево значений вместо таблицы — тогда обходите дерево рекурсивно по коллекции Строки каждого узла.
Работает ли обход через СКД в фоновом задании?
Да, но с оговоркой: методы ПолучитьИсполняемуюСхемуКомпоновкиДанных привязаны к элементу формы, поэтому в фоновом задании формы уже нет. Решение — сформировать таблицу значений в серверной процедуре формы и передать её в фоновое задание как параметр, либо хранить СКД отдельно (в макете) и собирать настройки программно в самом задании.
Чем отличается обход динамического списка от обхода таблицы значений на форме?
Таблица значений на форме — обычная коллекция, её можно перебирать напрямую циклом «Для Каждого». Динамический список коллекцией не является: это настройки запроса плюс порционно загружаемый результат. Поэтому для списка всегда нужен промежуточный шаг — получение данных через СКД, запрос или ДанныеСтроки.
Как учесть отборы пользователя при самостоятельном запросе без СКД?
Прочитайте коллекцию Элементы.Список.КомпоновщикНастроек.ПользовательскиеНастройки или фиксированные настройки отбора, извлеките из них элементы отбора (поле, вид сравнения, значение) и программно добавьте соответствующие условия в текст своего запроса с параметрами. Это трудоёмко, поэтому при возможности предпочтительнее механизм исполняемых настроек СКД.
Что важно запомнить
Динамический список нельзя обойти как коллекцию — данные получают через исполняемую СКД с выводом в таблицу значений, через самостоятельный запрос с выборкой или через ВыделенныеСтроки для отмеченных пользователем строк. Код получения данных выполняйте на сервере, учитывайте пользовательские отборы, защищайте цикл попыткой-исключением и контролируйте объём выгружаемых данных.