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