Перебор строк и работа с таблицей формы в 1С 8.3: код, ограничения и альтернативы

· 10 мин

Содержание

Обойти табличное поле в 1С — значит перебрать не сам элемент формы, а данные, к которым он привязан. В управляемой форме это Для Каждого Строка Из Объект.Товары Цикл, где Объект.Товары — путь к данным таблицы формы. Выделенные строки берут из Элементы.Товары.ВыделенныеСтроки, текущую — из Элементы.Товары.ТекущиеДанные.

Способ зависит от того, что лежит под таблицей. Табличная часть и таблица значений перебираются целиком циклом. Дерево значений требует рекурсии. Динамический список строк на клиенте не хранит вообще — его обходят запросом на сервере.

Перебор строк таблицы формы в коде

Элемент «Таблица формы» (в обычных формах — «Табличное поле») только показывает данные. Цикл всегда идёт по реквизиту, указанному в свойстве ПутьКДанным.

Все строки

&НаКлиенте
Процедура ПересчитатьСуммы(Команда)
	Для Каждого Строка Из Объект.Товары Цикл
		Строка.Сумма = Строка.Количество * Строка.Цена;
	КонецЦикла;
КонецПроцедуры

Тот же код работает и на сервере. Если строк больше нескольких сотен и расчёт тяжёлый, переносите его в серверную процедуру: один вызов сервера дешевле, чем долгий цикл в тонком клиенте.

Только выделенные строки

ВыделенныеСтроки у таблицы формы с табличной частью или таблицей значений — это массив идентификаторов, а не самих строк. Строку получают через НайтиПоИдентификатору:

&НаКлиенте
Процедура ОбнулитьСкидку(Команда)
	Для Каждого Ид Из Элементы.Товары.ВыделенныеСтроки Цикл
		Строка = Объект.Товары.НайтиПоИдентификатору(Ид);
		Если Строка <> Неопределено Тогда
			Строка.Скидка = 0;
		КонецЕсли;
	КонецЦикла;
КонецПроцедуры

У динамического списка в ВыделенныеСтроки лежат ключи записей — для справочников и документов это ссылки. Их передают на сервер и обрабатывают там.

Удаление строк в цикле

Удалять строки внутри Для Каждого нельзя: после удаления сдвигаются индексы и цикл пропускает соседнюю строку. Правильно — идти с конца, зафиксировав количество до начала цикла:

Всего = Объект.Товары.Количество();
Для Сч = 1 По Всего Цикл
	Индекс = Всего - Сч;
	Строка = Объект.Товары[Индекс];
	Если Строка.Количество = 0 Тогда
		Объект.Товары.Удалить(Индекс);
	КонецЕсли;
КонецЦикла;

При пяти строках цикл пройдёт индексы 4, 3, 2, 1, 0. Удаление строки с большим индексом не меняет номера строк перед ней, поэтому ничего не теряется.

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

Обычные формы

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

Если нужно перебрать строки ровно в том порядке и с тем отбором, которые видит пользователь, помните: сортировка и отбор в таблице формы касаются отображения. Цикл по Объект.Товары идёт в порядке хранения и по всем строкам.

Где стандартная таблица упирается в потолок

Таблица формы удобна, пока данные плоские и их немного. Узкие места видны сразу:

  • 📦 Объём. Таблица значений в реквизите формы целиком передаётся между сервером и клиентом. 50 000 строк по 15 колонок — это мегабайты трафика при каждом серверном вызове с контекстом.
  • 🌳 Иерархия. Плоская таблица не умеет сворачивать группы. Для вложенности нужен другой тип данных.
  • 🧩 Динамические колонки. Набор колонок задаётся в конфигураторе. Если он известен только в момент работы (например, по числу складов), колонки придётся создавать кодом.
  • 🎨 Оформление. Отдельную ячейку нельзя «покрасить» напрямую, только через условное оформление по правилу.
  • 🧾 Сложная вёрстка. Объединённые ячейки, шапки в несколько этажей, графика внутри — это уже не таблица формы.

У каждого ограничения есть свой обходной путь, и почти всегда без переписывания формы с нуля.

Дерево значений вместо плоского списка

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

Заполнение на сервере

&НаСервере
Процедура ЗаполнитьДерево()
	Дерево = РеквизитФормыВЗначение("ДеревоТоваров");
	Дерево.Строки.Очистить();

	Группа = Дерево.Строки.Добавить();
	Группа.Наименование = "Канцтовары";

	Товар = Группа.Строки.Добавить();
	Товар.Наименование = "Бумага А4";
	Товар.Количество = 10;

	ЗначениеВРеквизитФормы(Дерево, "ДеревоТоваров");
КонецПроцедуры

Рекурсивный обход

На клиенте у дерева нет свойства Строки — подчинённые элементы получают методом ПолучитьЭлементы():

&НаКлиенте
Функция СуммаПоУзлу(Элементы)
	Итог = 0;
	Для Каждого Узел Из Элементы Цикл
		Подчиненные = Узел.ПолучитьЭлементы();
		Если Подчиненные.Количество() = 0 Тогда
			Итог = Итог + Узел.Количество;
		Иначе
			Итог = Итог + СуммаПоУзлу(Подчиненные);
		КонецЕсли;
	КонецЦикла;
	Возврат Итог;
КонецФункции

// Вызов: Всего = СуммаПоУзлу(ДеревоТоваров.ПолучитьЭлементы());

На сервере после РеквизитФормыВЗначение обход тот же, только через Строка.Строки. Для поиска по всем уровням у коллекции строк дерева есть Найти и НайтиСтроки с параметром включения подчинённых.

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

Колонки и их видимость из кода

Скрыть или показать колонку

Простейший случай — колонка уже есть в форме:

Элементы.ТоварыСкидка.Видимость = ПолучитьФункциональнуюОпцию("ИспользоватьСкидки");
Элементы.ТоварыЦена.ТолькоПросмотр = Не РольДоступна("ПолныеПрава");

Имя элемента колонки смотрите в дереве элементов формы, оно обычно составлено из имени таблицы и имени поля.

Добавить колонку, которой не было в конфигураторе

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

&НаСервере
Процедура ДобавитьКолонкуСклада(ИмяКолонки, Заголовок)
	Добавляемые = Новый Массив;
	Добавляемые.Добавить(Новый РеквизитФормы(ИмяКолонки,
		Новый ОписаниеТипов("Число"), "ОстаткиПоСкладам", Заголовок));
	ИзменитьРеквизиты(Добавляемые);

	Поле = Элементы.Добавить("ОстаткиПоСкладам" + ИмяКолонки,
		Тип("ПолеФормы"), Элементы.ОстаткиПоСкладам);
	Поле.Вид = ВидПоляФормы.ПолеВвода;
	Поле.ПутьКДанным = "ОстаткиПоСкладам." + ИмяКолонки;
КонецПроцедуры

Здесь ОстаткиПоСкладам — реквизит типа таблица значений, третий параметр конструктора РеквизитФормы указывает, внутри какого реквизита создать колонку.

Добавленные кодом реквизиты не сохраняются в метаданных. При повторном открытии формы колонки создаются заново, обычно в ПриСозданииНаСервере. Перед повторным добавлением проверяйте, нет ли элемента с таким именем, иначе получите ошибку о неуникальном имени.

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

Постраничный вывод больших выборок

Динамический список уже работает порциями: платформа сама подгружает строки по мере прокрутки. Поэтому первое решение для больших справочников и журналов — динамический список, а не таблица значений.

Если вывод всё же собирается вручную (результат сложного расчёта, данные из внешней системы), делайте листание по ключу. В языке запросов 1С нет конструкции смещения, есть только ПЕРВЫЕ N. Приём такой:

  1. Отсортируйте выборку по уникальному полю — например, по коду или по дате с номером.
  2. Возьмите первые 100 строк, запомните значение ключа последней строки.
  3. Для следующей страницы добавьте условие «ключ больше запомненного» и снова ПЕРВЫЕ 100.
  4. Для кнопки «Назад» храните стек ключей начала каждой страницы.
Запрос = Новый Запрос;
Запрос.Текст =
	"ВЫБРАТЬ ПЕРВЫЕ 100
	|	Номенклатура.Ссылка КАК Ссылка,
	|	Номенклатура.Код КАК Код,
	|	Номенклатура.Наименование КАК Наименование
	|ИЗ
	|	Справочник.Номенклатура КАК Номенклатура
	|ГДЕ
	|	Номенклатура.Код > &ПоследнийКод
	|УПОРЯДОЧИТЬ ПО
	|	Код";
Запрос.УстановитьПараметр("ПоследнийКод", ПоследнийКод);

Число в ПЕРВЫЕ параметром не задаётся. Если размер страницы настраивается, подставляйте его в текст запроса через СтрЗаменить до выполнения.

Сравнение «больше» по строковому коду работает корректно, только если коды одной длины. Для числовых кодов или смешанных данных листайте по ссылке с сортировкой по ней же или по дате создания.

Оформление ячеек по условию

Покрасить отдельную ячейку в управляемой форме можно только правилом: «если условие — применить оформление к полю». Правила задают в свойствах формы или кодом через УсловноеОформление.

&НаСервере
Процедура ВыделитьОтрицательныеОстатки()
	Элемент = УсловноеОформление.Элементы.Добавить();

	Отбор = Элемент.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	Отбор.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Объект.Товары.Остаток");
	Отбор.ВидСравнения = ВидСравненияКомпоновкиДанных.Меньше;
	Отбор.ПравоеЗначение = 0;

	Элемент.Оформление.УстановитьЗначениеПараметра("ЦветТекста", WebЦвета.Красный);

	Поле = Элемент.Поля.Элементы.Добавить();
	Поле.Поле = Новый ПолеКомпоновкиДанных("ТоварыОстаток");
КонецПроцедуры

Пути здесь разные: в отборе указывается путь к данным (Объект.Товары.Остаток), а в поле оформления — имя элемента формы (ТоварыОстаток). Перепутанный путь — самая частая причина, по которой правило «не работает ».

Что ещё можно менять через оформление:

ПараметрЧто делает
ЦветТекста, ЦветФонацвет шрифта и заливки ячейки
Шрифтжирный, курсив, размер
Текстзаменяет отображаемое значение, например пустое поле на «не указано»
Видимостьскрывает значение в ячейке
ТолькоПросмотрзапрещает редактирование ячейки по условию

В обычных формах для того же есть событие табличного поля ПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки). В нём доступна каждая ячейка: ОформлениеСтроки.Ячейки.Остаток.ЦветФона. В управляемых формах такого события нет, поэтому логику «покрасить по сложной формуле» переносят в отдельную колонку-признак и делают правило по ней.

Поле HTML-документа и табличный документ для сложного вывода

Когда таблица формы уже не справляется с вёрсткой, есть два пути. Какой выбрать — по задаче:

ЭлементКогда братьОграничения
Поле табличного документаотчёты, печатные формы, сводные таблицы, объединённые шапки, расшифровка по кликуредактирование данных неудобное, это вывод, а не ввод
Поле HTML-документакарточки, графики на JavaScript, нестандартная вёрстка, встроенные справкиданные надо собирать в HTML строкой, поведение зависит от встроенного браузера клиента
Таблица формыввод и правка строк, списки с отборомплоская или иерархическая структура, фиксированная сетка

Для отчётов почти всегда лучше поле табличного документа: его заполняют макетом или схемой компоновки данных, а клик по ячейке ловят обработкой расшифровки. Пользователь может сохранить результат в Excel или PDF стандартными средствами.

Поле HTML-документа подключают к реквизиту типа строка. В реквизит записывают готовую разметку, а реакцию на клик обрабатывают в событии ПриНажатии(Элемент, ДанныеСобытия, СтандартнаяОбработка): по ДанныеСобытия можно понять, на какой элемент страницы нажали. Если в разметку подставляются данные из базы, экранируйте символы <, > и & — иначе одно наименование с угловой скобкой ломает всю страницу.

Что вам нужно показать пользователю?

Как не тормозить на больших массивах данных

Скорость таблицы формы чаще всего убивает не сам объём, а количество обращений к серверу. Проверенные приёмы:

  1. Один серверный вызов вместо цикла с вызовами. Обработка Для Каждого на клиенте, внутри которой вызывается серверная функция, делает столько вызовов, сколько строк. Соберите нужное в массив и передайте за раз.
  2. &НаСервереБезКонтекста там, где форма не нужна. Директива &НаСервере таскает на сервер весь контекст формы, включая таблицу. Если функции нужны только параметры, контекст ей не нужен.
  3. Временное хранилище для тяжёлых таблиц. Таблицу значений, которую не надо показывать целиком, кладите через ПоместитьВоВременноеХранилище и храните на форме только адрес.
  4. Минимум колонок в запросе. Каждая лишняя колонка в реквизите формы — это трафик при каждом вызове с контекстом.
  5. Динамический список для всего, что больше нескольких тысяч строк. Он читает только видимую порцию и не держит выборку в памяти формы.
  6. Без ТекущиеДанные в циклах. Это свойство про строку под курсором, перебирать через него таблицу бессмысленно и медленно.

Не переносите в форму таблицу на десятки тысяч строк ради «чтобы было под рукой» — у пользователей на удалённом рабочем столе или в веб-клиенте форма будет открываться десятки секунд.

Чтобы обработать все строки динамического списка с учётом отбора пользователя, не пытайтесь «прокрутить» список на клиенте. Передайте на сервер значения отбора и выполните запрос с теми же условиями — результат совпадёт с тем, что видно на экране, и не будет ограничен загруженной порцией.

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

Чем табличное поле отличается от таблицы формы?
Табличное поле — элемент обычных форм толстого клиента. Таблица формы — его аналог в управляемых формах. Принцип одинаковый: элемент показывает данные реквизита, а перебирают сам реквизит.
Как узнать номер текущей строки?
В управляемой форме возьмите Элементы.Товары.ТекущиеДанные и прочитайте поле НомерСтроки, если таблица связана с табличной частью. У таблицы значений номера нет, используйте индекс: Объект.Товары.Индекс(Строка) на сервере.
Почему цикл по таблице не видит отбор пользователя?
Отбор в таблице формы фильтрует только отображение. Данные под ним остаются полными, поэтому цикл проходит по всем строкам. Отбор нужно повторить в коде условием.
Можно ли перебрать все строки динамического списка на клиенте?
Нет. Клиент держит только загруженную порцию строк. Полный перебор делают запросом на сервере с теми же условиями отбора.
Почему после ИзменитьРеквизиты колонка пустая?
Реквизит создан, но поле формы не связано с ним или таблицу не заполнили после создания колонки. Проверьте ПутьКДанным поля и заполните таблицу уже после добавления реквизита.