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