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

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

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

Почему методы ИзменитьСтроку() и ЗакончитьРедактированиеСтроки() не вызывают пересчет

Проанализируем механику работы платформы с элементами управления. На управляемой форме таблица и размещенные внутри нее поля ввода разделены на разные уровни событий:

  1. События строки таблицы: методы ИзменитьСтроку() и ЗакончитьРедактированиеСтроки() предназначены для перевода всей строки в режим интерактивного редактирования и выхода из него. Они инициируют только табличные события: ПриНачалеРедактирования, ПередОкончаниемРедактирования и ПриОкончанииРедактирования.
  2. События конкретного поля (колонки): событие ПриИзменении привязано к элементу управления ПолеФормы, а не к таблице. Платформа генерирует его исключительно в ответ на физические действия пользователя в интерфейсе: ввод с клавиатуры, выбор значения из выпадающего списка или нажатие клавиши Enter с последующей потерей фокуса.
  3. Программное присвоение значения: строка кода вида Строка.Количество = НовоеКоличество; напрямую меняет данные в структуре памяти клиента, минуя события элементов интерфейса. Никакие автоматические триггеры при этом платформой не запускаются.

Кроме того, в исходной попытке циклического обхода присутствуют критические ошибки позиционирования:

Свойство ТекущаяСтрока для таблицы формы, связанной с коллекцией ДанныеФормыКоллекция, ожидает не индекс строки (0, 1, 2…), а уникальный числовой идентификатор строки. Позиционирование по числовому индексу из переменной цикла приводит к непредсказуемым ошибкам. Также при быстром переборе на клиенте свойство ТекущиеДанные не успевает синхронизироваться в рамках одной итерации, из-за чего изменения могут применяться к одной и той же строке повторно.

Способ 1. Пакетный пересчет на стороне сервера (рекомендуемый)

Выясним причину, почему пересчет на клиенте в цикле считается неоптимальным. Выполнение перебора на клиенте перегружает поток интерфейса, заставляет платформу тратить ресурсы на перерисовку элементов и требует передачи лишних вызовов. Наиболее правильным архитектурным решением является перенос всей расчетной логики на сторону сервера.

Разберем по шагам, как устроен этот подход:

  1. На клиенте вызывается серверная процедура формы без контекста или с контекстом формы.
  2. Коллекция Объект.Услуги обходится стандартным циклом выборки по строкам.
  3. Для каждой строки изменяется требуемое значение и вызывается отдельная процедура пересчета строки.
  4. После завершения цикла выполняется общий пересчет итоговых сумм документа.
  5. Форма целиком возвращается на клиент уже с обновленными данными, поэтому вызывать методы обновления не требуется.

Посмотрим на пример правильной серверной реализации:


&НаСервере
Процедура ПересчитатьТабличнуюЧастьНаСервере(НовоеКоличество)

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

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

&НаСервере
Процедура РассчитатьСуммуИСтавкиСтроки(СтрокаТЧ)

    СтрокаТЧ.Сумма = СтрокаТЧ.Количество * СтрокаТЧ.Цена;
    
    // Пример расчета суммы НДС
    Если ЗначениеЗаполнено(СтрокаТЧ.СтавкаНДС) Тогда
        СтрокаТЧ.СуммаНДС = СтрокаТЧ.Сумма * 0.20; // Или вызов логики из общего модуля
    КонецЕсли;

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

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

Способ 2. Прямой вызов обработчиков при невозможности серверного рефакторинга

Рассмотрим альтернативную ситуацию: конфигурация типовая, код формы закрыт от кардинальных изменений, либо расчет зашит глубоко внутри клиентского обработчика УслугиКоличествоПриИзменении, из которого логику нельзя быстро вынести на сервер.

В этом случае мы можем вызвать обработчик события явно, передав в него элемент управления. Однако для корректной работы необходимо обязательно спозиционировать таблицу через получение правильного идентификатора строки.

Разберем реализацию этого обхода на клиенте:


&НаКлиенте
Процедура ИзменитьИПересчитатьКоличествоНаКлиенте(НовоеКоличество)

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

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

Важное предостережение: клиентский обход через прямой вызов обработчика следует применять только как крайнюю меру. На документах с сотнями строк данный подход работает значительно медленнее серверного пакетного пересчета из-за постоянной синхронизации контекста таблицы формы.

← На главную