Как программно вызвать процедуру ПриИзменении для всех строк табличной части в управляемой форме?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

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

Основная сложность заключается в том, что стандартные обработчики событий написаны с использованием контекста формы, в частности, они обращаются к Элементы.Товары.ТекущиеДанные. Если мы просто запустим цикл по табличной части, свойство ТекущиеДанные останется указывать на ту строку, на которой стоит курсор пользователя, и пересчет сработает некорректно. Разберем подробно, как решить эту задачу.

Проблема использования ТекущихДанных в цикле

Рассмотрим типичный код обработчика события изменения номенклатуры в форме документа:


&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
    
    // Получаем текущую строку, на которой стоит курсор
    ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
    
    Если ТекущиеДанные = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Логика пересчета цены, зависящая от контекста формы
    ПолучитьЦенуСервер(ТекущиеДанные.Номенклатура, ТекущиеДанные.Цена);
    РассчитатьСумму(ТекущиеДанные);

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

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

Способ 1: Имитация действий пользователя (Смена текущей строки)

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

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

Разберем алгоритм по шагам:

  1. Запускаем цикл по коллекции данных (Объект.Товары), для которых ранее мог быть выполнен программный вывод таблицы значений на форму.
  2. В каждой итерации получаем идентификатор строки через метод ПолучитьИдентификатор().
  3. Присваиваем этот идентификатор свойству Элементы.Товары.ТекущаяСтрока.
  4. Вызываем процедуру обработчика события.

Пример реализации кода на клиенте:


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

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

Важные нюансы данного метода:

Работа с параметром "Элемент"

Обратите внимание, что процедура события ПриИзменении всегда принимает один обязательный параметр — Элемент. При ручном вызове мы должны решить, что туда передать.

Вариант А: Передача реального элемента формы.


// Обращаемся к коллекции подчиненных элементов таблицы
ТоварыНоменклатураПриИзменении(Элементы.Товары.ПодчиненныеЭлементы.ТоварыНоменклатура);

Вариант Б: Передача Неопределено.

Если внутри процедуры ТоварыНоменклатураПриИзменении этот параметр нигде не используется (например, не проверяется Элемент.Имя), то можно сэкономить код и вызвать её так:

ТоварыНоменклатураПриИзменении(Неопределено);

Однако, если внутри процедуры есть обращение к свойствам элемента, этот код вызовет ошибку. Поэтому вариант А надежнее.

Способ 2: Архитектурный (Рефакторинг кода)

Более правильным с точки зрения производительности и архитектуры подходом является разделение логики интерфейса и бизнес-логики. Вместо того чтобы привязывать расчеты к Элементы.Товары.ТекущиеДанные, следует вынести алгоритм расчета в отдельную процедуру.

Рассмотрим, как правильно организовать код:


// 1. Создаем общую процедуру расчета, которая принимает СТРОКУ ДАННЫХ, а не элемент формы
&НаКлиенте
Процедура РассчитатьСтрокуТовары(СтрокаТаблицы)
    
    // Вся логика пересчета переносится сюда
    // Вместо ТекущиеДанные используем переданный параметр СтрокаТаблицы
    
    Цена = ПолучитьЦенуССервера(СтрокаТаблицы.Номенклатура);
    СтрокаТаблицы.Цена = Цена;
    СтрокаТаблицы.Сумма = СтрокаТаблицы.Цена * СтрокаТаблицы.Количество;
    
КонецПроцедуры

// 2. В стандартном обработчике вызываем нашу процедуру
&НаКлиенте
Процедура ТоварыНоменклатураПриИзменении(Элемент)
    
    ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
    Если ТекущиеДанные <> Неопределено Тогда
        РассчитатьСтрокуТовары(ТекущиеДанные);
    КонецЕсли;
    
КонецПроцедуры

// 3. Теперь массовый пересчет становится тривиальным и быстрым
&НаКлиенте
Процедура ОбновитьВсеЦены(Команда)
    
    Для Каждого Стр Из Объект.Товары Цикл
        // Просто передаем строку данных, не трогая интерфейс
        РассчитатьСтрокуТовары(Стр);
    КонецЦикла;
    
КонецПроцедуры

Преимущества архитектурного подхода:

Заключение

Мы рассмотрели два основных метода решения задачи:

  1. Если вам нужно быстро решить проблему "здесь и сейчас", и вы не хотите менять логику существующей формы, используйте трюк с Элементы.Товары.ТекущаяСтрока = СтрокаТЧ.ПолучитьИдентификатор().
  2. Если вы разрабатываете функционал с нуля или имеете возможность рефакторинга, настоятельно рекомендуем вынести логику расчета в отдельную процедуру, принимающую строку данных (ДанныеФормыЭлементКоллекции) в качестве параметра.
← На главную