При разработке на управляемых формах 1С часто возникает задача массового пересчета данных в табличной части документа. Например, необходимо обновить цены во всех строках после смены типа цен или валюты документа. В типовых конфигурациях логика пересчета (заполнение цены, суммы, ставки НДС) часто сосредоточена в обработчике события ПриИзменении поля номенклатуры или количества.
Основная сложность заключается в том, что стандартные обработчики событий написаны с использованием контекста формы, в частности, они обращаются к Элементы.Товары.ТекущиеДанные. Если мы просто запустим цикл по табличной части, свойство ТекущиеДанные останется указывать на ту строку, на которой стоит курсор пользователя, и пересчет сработает некорректно. Разберем подробно, как решить эту задачу.
Рассмотрим типичный код обработчика события изменения номенклатуры в форме документа:
&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
// Получаем текущую строку, на которой стоит курсор
ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
// Логика пересчета цены, зависящая от контекста формы
ПолучитьЦенуСервер(ТекущиеДанные.Номенклатура, ТекущиеДанные.Цена);
РассчитатьСумму(ТекущиеДанные);
КонецПроцедуры
Если мы попытаемся вызвать эту процедуру в цикле Для каждого по табличной части Объект.Товары (в которой может быть выполнено программное создание колонок), то переменная ТекущиеДанные внутри процедуры всегда будет возвращать одну и ту же строку (активную в интерфейсе), а не ту, которую мы перебираем в цикле.
Самый быстрый способ решения, не требующий глубокого рефакторинга кода — это программное переключение активной строки в таблице формы. В управляемых формах таблица (элемент формы) связана с данными через идентификаторы строк.
Чтобы метод Элементы.Товары.ТекущиеДанные вернул нужную нам строку, мы должны внутри цикла принудительно установить свойство ТекущаяСтрока у элемента формы.
Разберем алгоритм по шагам:
Объект.Товары), для которых ранее мог быть выполнен программный вывод таблицы значений на форму.ПолучитьИдентификатор().Элементы.Товары.ТекущаяСтрока.Пример реализации кода на клиенте:
&НаКлиенте
Процедура ОбновитьЦеныВоВсехСтроках(Команда)
// Проходим по всем строкам табличной части объекта
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
// 1. Устанавливаем фокус формы на конкретную строку
// Это заставляет Элементы.Товары.ТекущиеДанные вернуть данные именно этой строки
Элементы.Товары.ТекущаяСтрока = СтрокаТЧ.ПолучитьИдентификатор();
// 2. Вызываем стандартный обработчик "ПриИзменении"
// В качестве параметра можно передать сам элемент или Неопределено (см. ниже)
ТоварыНоменклатураПриИзменении(Элементы.Товары.ПодчиненныеЭлементы.ТоварыНоменклатура);
// При необходимости вызываем и другие обработчики
ТоварыКоличествоПриИзменении(Элементы.Товары.ПодчиненныеЭлементы.ТоварыКоличество);
КонецЦикла;
// Опционально: Обновляем отображение таблицы, если платформа не сделала это сама
Элементы.Товары.Обновить();
КонецПроцедуры
Важные нюансы данного метода:
ТекущаяСтрока вызывает синхронизацию с интерфейсом. Чтобы избежать проблем с производительностью, стоит использовать приемы построения эффективных интерфейсов.Элементы). Иногда добавление реквизитов и элементов на форму проще реализовать без написания кода.Обратите внимание, что процедура события ПриИзменении всегда принимает один обязательный параметр — Элемент. При ручном вызове мы должны решить, что туда передать.
Вариант А: Передача реального элемента формы.
// Обращаемся к коллекции подчиненных элементов таблицы
ТоварыНоменклатураПриИзменении(Элементы.Товары.ПодчиненныеЭлементы.ТоварыНоменклатура);
Вариант Б: Передача Неопределено.
Если внутри процедуры ТоварыНоменклатураПриИзменении этот параметр нигде не используется (например, не проверяется Элемент.Имя), то можно сэкономить код и вызвать её так:
ТоварыНоменклатураПриИзменении(Неопределено);
Однако, если внутри процедуры есть обращение к свойствам элемента, этот код вызовет ошибку. Поэтому вариант А надежнее.
Более правильным с точки зрения производительности и архитектуры подходом является разделение логики интерфейса и бизнес-логики. Вместо того чтобы привязывать расчеты к Элементы.Товары.ТекущиеДанные, следует вынести алгоритм расчета в отдельную процедуру.
Рассмотрим, как правильно организовать код:
// 1. Создаем общую процедуру расчета, которая принимает СТРОКУ ДАННЫХ, а не элемент формы
&НаКлиенте
Процедура РассчитатьСтрокуТовары(СтрокаТаблицы)
// Вся логика пересчета переносится сюда
// Вместо ТекущиеДанные используем переданный параметр СтрокаТаблицы
Цена = ПолучитьЦенуССервера(СтрокаТаблицы.Номенклатура);
СтрокаТаблицы.Цена = Цена;
СтрокаТаблицы.Сумма = СтрокаТаблицы.Цена * СтрокаТаблицы.Количество;
КонецПроцедуры
// 2. В стандартном обработчике вызываем нашу процедуру
&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
Если ТекущиеДанные <> Неопределено Тогда
РассчитатьСтрокуТовары(ТекущиеДанные);
КонецЕсли;
КонецПроцедуры
// 3. Теперь массовый пересчет становится тривиальным и быстрым
&НаКлиенте
Процедура ОбновитьВсеЦены(Команда)
Для Каждого Стр Из Объект.Товары Цикл
// Просто передаем строку данных, не трогая интерфейс
РассчитатьСтрокуТовары(Стр);
КонецЦикла;
КонецПроцедуры
Преимущества архитектурного подхода:
Мы рассмотрели два основных метода решения задачи:
Элементы.Товары.ТекущаяСтрока = СтрокаТЧ.ПолучитьИдентификатор().ДанныеФормыЭлементКоллекции) в качестве параметра.