При разработке на платформе 1С:Предприятие 8.3 в управляемом интерфейсе мы часто сталкиваемся с задачей пакетной обработки данных. Типичный сценарий: данные загружаются в табличную часть (например, из файла Excel), пользователь выделяет на форме нужные ему строки, нажимает кнопку, и программа должна изменить определенные ячейки только в выделенных строках. На первый взгляд, задача кажется предельно простой, но при ее реализации разработчики часто попадают в неочевидную ловушку, связанную с нумерацией строк.
Давайте подробно разберем, как правильно работать с коллекцией выделенных строк, выясним причину странного поведения индексов и проанализируем популярные ошибки, чтобы в будущем писать только быстрый и надежный код.
Рассмотрим классическую ситуацию. Мы получаем массив выделенных строк из элемента формы. Изначально мы можем написать простой цикл, предполагая, что свойство ВыделенныеСтроки возвращает нам массив порядковых номеров (индексов) строк в таблице. При первой загрузке данных (например, 45 строк) всё может отработать нормально. Но что произойдет, если мы очистим таблицу с помощью метода Очистить и загрузим новые данные из другого файла?
Мы увидим, что значения в массиве ВыделенныеСтроки начинаются не с нуля, а продолжаются с номера последней строки предыдущей загрузки (например, 46, 47, 48...). Система ведет себя так, будто таблица не очищалась, а увеличилась.
Проанализируем причину такого поведения. Дело в том, что в управляемых формах свойство формы ВыделенныеСтроки содержит не индексы (порядковые номера) строк, а их уникальные идентификаторы. Управляемые формы используют идентификаторы для того, чтобы связь формы с конкретной строкой данных не терялась при сортировках, наложении пользовательских отборов или удалении смежных строк.
Когда мы вызываем метод очистки коллекции, мы удаляем сами данные. Однако внутренний счетчик управляемой формы, который генерирует эти идентификаторы при добавлении новых строк, продолжает свою работу. Программно сбросить этот счетчик нельзя — он обнулится только при полном закрытии и повторном открытии формы обработки.
Именно поэтому пытаться привязываться к «нулевому» идентификатору или математически высчитывать смещение для новых строк — это изначально неверный подход, который неминуемо приведет к ошибкам.
Разберем по шагам, как нужно действовать. Код всегда должен опираться исключительно на значения идентификаторов, без оглядки на то, какими по счету они были созданы. Для получения самой строки данных по ее идентификатору платформа 1С предоставляет специальный метод табличных частей и таблиц значений — НайтиПоИдентификатору.
Посмотрим на пример правильного кода для обработки выделенных строк на клиенте:
&НаКлиенте
Процедура ОбработатьВыделенныеСтроки(Команда)
// Получаем массив идентификаторов выделенных строк
МассивИдентификаторов = Элементы.ТЧДанные.ВыделенныеСтроки;
Для Каждого Идентификатор Из МассивИдентификаторов Цикл
// Получаем саму строку данных по идентификатору
СтрокаДанных = Объект.ТЧДанные.НайтиПоИдентификатору(Идентификатор);
Если СтрокаДанных <> Неопределено Тогда
// Выполняем нужные манипуляции с данными строки
СтрокаДанных.ЕдИзм = ПолучитьКодЕд();
КонецЕсли;
КонецЦикла;
КонецПроцедуры
В этом коде мы перебираем массив идентификаторов, для каждого из них находим реальную строку в данных объекта и безопасно меняем нужное значение. Обязательно проверяйте результат поиска на Неопределено — это правило хорошего тона, защищающее код от случайных ошибок (например, если строка была удалена другим процессом в момент обработки).
Важно понимать, что подход, описанный выше, применим только для табличных частей объекта или таблиц значений, являющихся реквизитами формы. Если же мы работаем с Динамическим списком, ситуация меняется.
Динамический список не является классической коллекцией значений. Он подгружает данные из базы данных порциями, и у него нет метода НайтиПоИдентификатору. Для того чтобы получить данные выделенной строки в динамическом списке, нам необходимо использовать метод элемента формы ДанныеСтроки.
Рассмотрим, как это выглядит в коде:
&НаКлиенте
Процедура ОбработатьВыделенныеСтрокиДинамическогоСписка(Команда)
МассивИдентификаторов = Элементы.ДинамическийСписок.ВыделенныеСтроки;
Для Каждого Идентификатор Из МассивИдентификаторов Цикл
// Получаем структуру с данными строки динамического списка
Данные = Элементы.ДинамическийСписок.ДанныеСтроки(Идентификатор);
Если Данные <> Неопределено Тогда
// Получаем ссылку на объект базы данных
СсылкаНаОбъект = Данные.Ссылка;
// Далее можно передать ссылку на сервер для обработки
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Часто начинающие программисты, пытаясь найти способ обратиться к выделенной строке, прибегают к программной смене курсора (фокуса) таблицы. Рассмотрим пример такого неправильного кода:
// ВНИМАНИЕ: ТАК ДЕЛАТЬ НЕЛЬЗЯ!
ВыдСтроки = Элементы.Таблица.ВыделенныеСтроки;
Для Каждого ТекВыд Из ВыдСтроки Цикл
Элементы.Таблица.ТекущаяСтрока = ТекВыд; // Изменение фокуса
НужныеМнеДанные = Элементы.Таблица.ТекущиеДанные;
КонецЦикла;
Проанализируем, почему этот код является антипаттерном. Смена текущей строки программно внутри цикла заставляет систему каждый раз перерисовывать фокус интерфейса и провоцирует многократный вызов события формы ПриСменеТекущейСтроки. Если в этом обработчике у вас написан какой-либо тяжелый код (например, пересчет итогов, обновление связанных данных в других таблицах или скрытие элементов), система начнет катастрофически «тормозить». Форма может просто зависнуть на время выполнения цикла. Всегда обращайтесь к данным напрямую через идентификаторы, не меняя пользовательский фокус в интерфейсе.
Если вам нужно обработать большое количество выделенных строк табличной части (сотни или тысячи строк), выполнять цикл с изменением данных на клиенте (в директиве &НаКлиенте) — не самое оптимальное архитектурное решение. Множественные изменения данных формы на клиенте могут вызывать задержки.
Лучшей практикой является передача массива идентификаторов на сервер. На сервере цикл отработает значительно быстрее, там вы сможете полноценно использовать методы серверных объектов базы данных и вернуть на клиент уже готовый, полностью измененный результат.
Разберем пример серверной оптимизации:
&НаКлиенте
Процедура ЗаполнитьДанные(Команда)
МассивИдентификаторов = Элементы.ТЧДанные.ВыделенныеСтроки;
Если МассивИдентификаторов.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Передаем массив выделенных строк на сервер
ЗаполнитьДанныеНаСервере(МассивИдентификаторов);
КонецПроцедуры
&НаСервере
Процедура ЗаполнитьДанныеНаСервере(МассивИдентификаторов)
// Перебираем массив переданных идентификаторов на сервере
Для Каждого Идентификатор Из МассивИдентификаторов Цикл
СтрокаТЧ = Объект.ТЧДанные.НайтиПоИдентификатору(Идентификатор);
Если СтрокаТЧ <> Неопределено Тогда
СтрокаТЧ.Обработано = Истина;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
При пакетном изменении данных таблицы, ее принудительном обновлении или перезаполнении, платформа 1С может автоматически сбросить пользовательское выделение строк. Для хорошего пользовательского опыта (UX) крайне важно сохранять выбор пользователя, чтобы после выполнения операции строки остались выделенными.
Для реализации этого механизма перед началом обработки массив ВыделенныеСтроки копируется в отдельную переменную-массив. После завершения всех операций сохраненные идентификаторы добавляются обратно в коллекцию Элементы.ИмяТаблицы.ВыделенныеСтроки.
В конфигурациях, построенных на базе Библиотеки стандартных подсистем (БСП), для этой задачи предусмотрены готовые клиентские процедуры, например функция ВосстановитьВыделенныеСтроки, которая значительно упрощает разработку эргономичных интерфейсов.
Применяя описанные подходы, вы сможете создавать стабильные обработки, которые корректно работают с таблицами управляемых форм 1С, не зависают при переборе элементов и оставляют приятное впечатление у конечных пользователей.