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