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