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

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

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

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

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

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


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

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

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

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

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

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

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

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

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


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

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

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

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

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

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


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

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

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


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

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

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

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

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


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

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

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

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

Заключение

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

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