К статье
Практический материал

Разработка и Конфигурация · 12 мин

Как обойти табличное поле в 1С: практическое руководство

Как обойти табличное поле в 1С: перебор строк табличной части, таблицы значений и динамического списка. Примеры кода, ошибки, различия для управляемых и обычных форм.

Обход табличного поля — одна из самых частых задач при доработке конфигураций 1С: нужно просуммировать колонку, найти строку по условию, проверить заполнение или перенести данные в другой объект. Проблема в том, что «табличное поле» — это элемент формы, а данные лежат в связанном с ним реквизите: табличной части документа, таблице значений или динамическом списке. Обходить нужно именно данные, а не визуальный элемент, иначе код окажется привязанным к конкретной форме и сломается при её изменении.

Способ обхода зависит от двух вещей: где выполняется код (клиент или сервер, управляемая или обычная форма, модуль объекта) и какой тип данных стоит за табличным полем. Ниже разобраны рабочие варианты для каждой ситуации с готовыми фрагментами кода, которые можно переносить в свою конфигурацию без доработки.

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

Прежде чем писать цикл, посмотрите в свойствах табличного поля реквизит «ПутьКДанным» (в управляемых формах) или «Данные» (в обычных). Именно он показывает, что вы будете перебирать:

  • Объект.Товары — табличная часть документа или справочника;
  • Товары (реквизит формы типа «ТаблицаЗначений») — динамическая таблица, не связанная с объектом напрямую;
  • Список — динамический список, его данные формируются запросом и недоступны для прямого перебора;
  • ДеревоЗначений — иерархическая структура, требует рекурсивного обхода.

Типичная ошибка новичка — попытаться обойти сам элемент формы: Элементы.Товары.Строки не существует, у элемента формы нет коллекции строк данных. Элемент управляет только отображением: текущей строкой, выделением, видимостью колонок.

Обход табличной части документа

Самый распространённый случай. В модуле объекта или в модуле формы (где доступен реквизит Объект) табличная часть перебирается циклом «Для Каждого»:

Для Каждого СтрокаТовары Из Объект.Товары Цикл

СтрокаТовары.Сумма = СтрокаТовары.Количество * СтрокаТовары.Цена;

КонецЦикла;

Если нужен индекс строки (например, для сообщения пользователю с номером строки), используйте цикл «Для»:

Для Инд = 0 По Объект.Товары.Количество() - 1 Цикл

СтрокаТовары = Объект.Товары[Инд];

Если СтрокаТовары.Количество = 0 Тогда

Сообщить("В строке " + (Инд + 1) + " не указано количество");

КонецЕсли;

КонецЦикла;

Номер строки для пользователя получают через свойство СтрокаТовары.НомерСтроки — оно соответствует тому, что видно в колонке «N» на форме. Не путайте его с индексом: индекс начинается с нуля, номер строки — с единицы.

Обход таблицы значений на форме

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

Базовый обход на клиенте:

Для Каждого СтрокаТЗ Из Товары Цикл

Если СтрокаТЗ.Пометка Тогда

// обработка отмеченной строки

КонецЕсли;

КонецЦикла;

Если нужны методы НайтиСтроки(), Сортировать(), Свернуть() — переносите обработку на сервер. Для этого реквизит формы преобразуют в таблицу значений и обратно:

&НаСервере

Процедура ОбработатьНаСервере()

ТЗ = РеквизитФормыВЗначение("Товары");

Для Каждого СтрокаТЗ Из ТЗ.Строки Цикл

// любые операции с полноценной таблицей значений

КонецЦикла;

ЗначениеВРеквизитФормы(ТЗ, "Товары");

КонецПроцедуры

Учтите: преобразование РеквизитФормыВЗначение — дорогая операция, она копирует все данные. На больших таблицах (тысячи строк) делайте серверный вызов один раз и выполняйте всю обработку в нём, а не дергайте сервер из цикла на клиенте.

Обход выделенных строк и текущей строки

Часто нужно обработать не всю таблицу, а только строки, которые пользователь выделил мышью. Для этого используется свойство элемента формы ВыделенныеСтроки — массив идентификаторов:

Для Каждого ИдСтроки Из Элементы.Товары.ВыделенныеСтроки Цикл

СтрокаДанные = Объект.Товары.НайтиПоИдентификатору(ИдСтроки);

Если СтрокаДанные <> Неопределено Тогда

СтрокаДанные.Пометка = Истина;

КонецЕсли;

КонецЦикла;

Ключевой момент: в ВыделенныеСтроки лежат не индексы и не номера строк, а идентификаторы коллекции данных формы. Получить саму строку можно только через НайтиПоИдентификатору(). Попытка обратиться Объект.Товары[ИдСтроки] даст неверную строку или ошибку.

Текущая строка (та, где стоит курсор) доступна через Элементы.Товары.ТекущаяСтрока — это тоже идентификатор, а готовые данные строки — через Элементы.Товары.ТекущиеДанные. Второй вариант удобнее, если нужно просто прочитать значения колонок.

Типичные ошибки
01
Использование идентификатора из ВыделенныеСтроки как индекса массива — получаете не ту строку или исключение
02
Удаление строк внутри прямого цикла «Для Каждого» — коллекция меняется во время перебора, часть строк пропускается
03
Обращение к НайтиСтроки() на клиенте управляемой формы — метода нет у ДанныеФормыКоллекция, код падает
04
Серверный вызов внутри цикла по каждой строке — форма зависает на больших таблицах

Обход динамического списка

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

Правильный подход — повторить запрос списка и обойти результат выборки:

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| Реализация.Ссылка КАК Ссылка,

| Реализация.СуммаДокумента КАК Сумма

|ИЗ

| Документ.РеализацияТоваровУслуг КАК Реализация

|ГДЕ

| Реализация.Дата МЕЖДУ &Начало И &Конец";

// подстановка параметров и обход

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл

// обработка записи

КонецЦикла;

Текст запроса возьмите из свойства «Запрос» динамического списка или из настройки «Произвольный запрос». Отборы, установленные пользователем, придётся воспроизводить вручную через параметры — учитывайте это, если логика зависит от текущего отбора в списке.

Обход дерева значений и удаление строк в цикле

Дерево значений требует рекурсии, потому что у каждой строки могут быть вложенные строки:

Процедура ОбойтиДерево(СтрокиДерева)

Для Каждого СтрокаДерева Из СтрокиДерева Цикл

// обработка текущей строки

ОбойтиДерево(СтрокаДерева.Строки); // вложенный уровень

КонецЦикла;

КонецПроцедуры

// вызов: ОбойтиДерево(Дерево.ПолучитьЭлементы()); — для формы

Отдельный частый сценарий — удаление строк по условию. Прямой цикл здесь опасен: при удалении элемента индексы сдвигаются, и цикл пропускает строки. Безопасные варианты — обратный обход по индексу либо сбор строк в массив с последующим удалением:

// Вариант 1: обратный обход

Для Инд = 1 По Объект.Товары.Количество() Цикл

СтрокаТовары = Объект.Товары[Объект.Товары.Количество() - Инд];

Если СтрокаТовары.Количество = 0 Тогда

Объект.Товары.Удалить(СтрокаТовары);

КонецЕсли;

КонецЦикла;

// Вариант 2: через массив

СтрокиКУдалению = Новый Массив;

Для Каждого СтрокаТовары Из Объект.Товары Цикл

Если СтрокаТовары.Количество = 0 Тогда

СтрокиКУдалению.Добавить(СтрокаТовары);

КонецЕсли;

КонецЦикла;

Для Каждого Строка Из СтрокиКУдалению Цикл

Объект.Товары.Удалить(Строка);

КонецЦикла;

Для таблицы значений на сервере есть третий путь: НайтиСтроки(Отбор) возвращает массив найденных строк, которые затем удаляются или изменяются — это короче и читабельнее ручного цикла.

Сравнение вариантов

Для Каждого ... Из

Универсальный обход всех строк. Подходит всегда, но нельзя удалять строки внутри цикла

Цикл Для по индексу

Нужен, когда важен индекс или идёт удаление в обратном порядке. Чуть более громоздкий

НайтиСтроки() с отбором

Быстрый поиск подмножества строк без полного перебора. Только на сервере для таблиц значений

Запрос к динамическому списку

Единственный способ обойти данные списка. Требует дублирования отборов вручную

Проверка результата и производительность

После написания цикла проверьте его на граничных случаях: пустая таблица (цикл не должен падать — «Для Каждого» просто не выполнится ни разу), одна строка, таблица с тысячами строк. Убедитесь, что сообщения пользователю выводятся с понятными номерами строк, а изменённые данные реально сохраняются в объекте.

Для тяжёлых обработок замерьте время выполнения через ТекущаяУниверсальнаяДатаВМиллисекундах() до и после цикла. Если обход занимает секунды, ищите внутри цикла скрытые запросы к базе: обращения вида СтрокаТовары.Номенклатура.Артикул в клиент-серверном режиме могут порождать обращения к СУБД на каждой итерации. Выносите такие данные в один запрос с соединением по номенклатуре.

Контрольные точки

Как проверить результат

Пустая табличная часть
Норма

Цикл завершается без ошибок, обработка ничего не делает

Тревожный признак

Ошибка обращения к строке или деление на ноль

Удаление строк по условию
Норма

Удалены все подходящие строки, остальные на месте

Тревожный признак

Часть строк с условием осталась — признак удаления в прямом цикле

Выделенные строки
Норма

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

Тревожный признак

Обработаны другие строки — идентификатор использован как индекс

Большая таблица
Норма

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

Тревожный признак

Форма подвисает, в замере видны множественные обращения к серверу

Итоговая логика выбора проста: определили источник данных табличного поля → выбрали подходящий цикл → проверили поведение на пустой и большой таблице. В большинстве задач достаточно «Для Каждого» по табличной части объекта, а экзотика вроде динамических списков решается запросом.

Итог

Что важно запомнить

Обходят не элемент формы, а данные за ним. Табличная часть — цикл «Для Каждого», выделенные строки — через НайтиПоИдентификатору, динамический список — только запросом, удаление строк — обратным циклом или через массив.

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

Как узнать количество строк в табличном поле?

Через коллекцию данных: Объект.Товары.Количество() для табличной части или Товары.Количество() для таблицы значений на форме. У самого элемента формы такого свойства нет. Для динамического списка точное количество строк получают только запросом с функцией КОЛИЧЕСТВО.

Можно ли обойти табличное поле на клиенте без вызова сервера?

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

Почему цикл пропускает строки при удалении?

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

Как обойти только строки с установленной пометкой?

Добавьте проверку внутрь цикла: Если СтрокаТовары.Пометка Тогда ... КонецЕсли;. На сервере для таблицы значений эффективнее использовать ТЗ.НайтиСтроки(Новый Структура("Пометка", Истина)) — получите сразу массив нужных строк без полного перебора.

Чем ТекущиеДанные отличается от ТекущаяСтрока?

ТекущаяСтрока — это идентификатор строки (число), пригодный для НайтиПоИдентификатору. ТекущиеДанные — готовый объект с данными строки, из которого сразу читаются значения колонок. Для чтения значений используйте ТекущиеДанные, для передачи строки между процедурами — идентификатор.

Как обойти табличное поле в обычной форме (8.2)?

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