При переходе на управляемые формы (УФ) в платформе 1С:Предприятие 8 разработчики часто сталкиваются с отсутствием привычных инструментов из прошлых версий. В 1С 7.7 существовал популярный прием — размещение на форме текстового реквизита с формулой, которая вызывалась при любом обновлении экрана. В обычных формах 1С 8.x было стандартное событие ПриИзмененииДанных. Однако в управляемом приложении концепция кардинально изменилась: разделение на клиент и сервер накладывает жесткие ограничения на оптимизацию трафика, поэтому прямого и простого аналога «глобального» события изменения формы в УФ нет. Быстро освоить работу с событиями и реквизитами обычных и управляемых форм поможет шпаргалка разработчика по работе с формами.
В этой статье мы подробно разберем, как решить задачу автоматического пересчета данных при изменении любого реквизита формы, не создавая при этом десятки однотипных обработчиков вручную.
Для понимания архитектуры управляемого приложения важно уяснить: платформа стремится минимизировать объем данных, передаваемых между клиентом и сервером. Если бы существовало глобальное событие, срабатывающее при абсолютно любом изменении любого реквизита, это привело бы к колоссальным задержкам интерфейса из-за постоянных неконтролируемых вызовов серверных процедур. Тем не менее, задачу отслеживания изменений решать нужно. Рассмотрим четыре наиболее эффективных и стандартных метода реализации такого механизма.
Вместо того чтобы вручную прописывать событие ПриИзменении для каждого поля в конфигураторе, мы можем автоматизировать этот процесс на этапе инициализации формы. Этот метод считается самым надежным, гибким и производительным. Для динамической настройки элементов и интерфейса можно использовать инструмент для программного изменения управляемых форм.
Суть метода заключается в обходе дерева элементов формы при ее создании и динамическом назначении единой клиентской процедуры-обработчика для всех интерактивных полей ввода.
Разберем по шагам, как это реализовать. В модуле формы напишем процедуру обхода элементов:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Запускаем рекурсивный обход всех элементов формы
НазначитьОбщийОбработчикИзмененияЭлементов(ЭтотОбъект.Элементы);
КонецПроцедуры
&НаСервере
Процедура НазначитьОбщийОбработчикИзмененияЭлементов(КоллекцияЭлементов)
Для Каждого Элемент Из КоллекцияЭлементов Цикл
// Если элемент является группой, рекурсивно обходим ее содержимое
Если ТипЗнч(Элемент) = Тип("ГруппаФормы") Тогда
НазначитьОбщийОбработчикИзмененияЭлементов(Элемент.ПодчиненныеЭлементы);
// Если элемент — таблица, обходим ее колонки
ИначеЕсли ТипЗнч(Элемент) = Тип("ТаблицаФормы") Тогда
НазначитьОбщийОбработчикИзмененияЭлементов(Элемент.ПодчиненныеЭлементы);
// Проверяем, является ли элемент полем ввода, флажком или переключателем
ИначеЕсли ТипЗнч(Элемент) = Тип("ПолеФормы") Тогда
Если Элемент.Вид = ВидПоляФормы.ПолеВвода
Или Элемент.Вид = ВидПоляФормы.ПолеФлажка
Или Элемент.Вид = ВидПоляФормы.ПолеПереключателя Тогда
// Исключаем системные или служебные поля, которые не должны влиять на пересчет
Если Элемент.Имя = "ИмяСлужебногоПоля" Тогда
Продолжить;
КонецЕсли;
// Динамически привязываем клиентское событие
Попытка
Элемент.УстановитьДействие("ПриИзменении", "Подключаемый_ОбщийОбработчикПриИзменении");
Исключение
// Обработка исключения на случай, если у элемента нет события "ПриИзменении"
КонецПопытки;
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Теперь создадим на клиенте процедуру, которая будет вызываться каждый раз, когда пользователь меняет значение любого из подключенных полей:
&НаКлиенте
Процедура Подключаемый_ОбщийОбработчикПриИзменении(Элемент)
// В параметр "Элемент" передается конкретный элемент формы, значение которого изменилось.
// Это позволяет гибко реагировать на изменения.
ВыполнитьПересчетДанныхФормы(Элемент.Имя);
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьПересчетДанныхФормы(ИмяИзмененногоЭлемента)
// Здесь мы пишем логику пересчета.
// Например, если изменилась цена или количество, пересчитываем сумму:
Если ИмяИзмененногоЭлемента = "Цена" Или ИмяИзмененногоЭлемента = "Количество" Тогда
Объект.Сумма = Объект.Цена * Объект.Количество;
КонецЕсли;
// Если требуется общий тяжелый пересчет, вызываем серверную процедуру
// Но помните о производительности: серверные вызовы должны быть обоснованы!
КонецПроцедуры
Преимущества метода:
Недостатки: метод перехватывает только интерактивные действия пользователя. Если реквизит формы меняется программно из другого участка кода, событие ПриИзменении вызвано не будет.
У управляемой формы существует стандартное предопределенное событие ОбновлениеОтображения (в англоязычном варианте платформы — RefreshDisplay). Платформа вызывает его автоматически при изменении состояния формы, смене фокуса, активизации различных элементов и обновлении экранных данных.
Проанализируем пример использования этого события:
&НаКлиенте
Процедура ОбновлениеОтображения(Отказ)
// Пример легкого клиентского пересчета или управления видимостью элементов
Элементы.ГруппаСкидок.Доступность = (Объект.СуммаДокумента > 10000);
КонецПроцедуры
Важное предупреждение! Это событие вызывается платформой чрезвычайно часто — практически при каждом движении мыши, переходе между ячейками табличной части или изменении активности окон. Помещать в него ресурсоемкие вычисления, а тем более выполнять вызовы сервера (директив &НаСервере или &НаСервереБезКонтекста) категорически запрещено! Это приведет к критическому замедлению работы интерфейса 1С и зависанию программы у пользователя.
Используйте ОбновлениеОтображения исключительно для легких визуальных операций на клиенте (скрыть/показать кнопку, заблокировать доступность поля).
Если мгновенный автоматический расчет данных «на лету» не является строго обязательным условием, лучшим решением с точки зрения оптимизации будет отказ от постоянного мониторинга полей ввода. Вместо этого мы можем зафиксировать факт изменений непосредственно перед записью документа или закрытием формы.
Разберем по шагам, как реализовать сравнение состояний:
Шаг 1. При открытии формы или чтении данных сохраняем первоначальное состояние ключевых реквизитов формы в структуру:
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
// Сохраняем исходные значения в специальный реквизит формы "ИсходныеДанные" (тип: Произвольный)
ИсходныеДанные = Новый Структура;
ИсходныеДанные.Вставить("Контрагент", ТекущийОбъект.Контрагент);
ИсходныеДанные.Вставить("СуммаДокумента", ТекущийОбъект.СуммаДокумента);
ИсходныеДанные.Вставить("Договор", ТекущийОбъект.Договор);
КонецПроцедуры
Шаг 2. При записи документа проверяем, изменились ли данные, и при необходимости запускаем алгоритмы пересчета или валидации:
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Сравниваем текущее состояние реквизитов с сохраненным исходным состоянием
Если ИсходныеДанные.Контрагент <> ТекущийОбъект.Контрагент Тогда
// Контрагент был изменен — выполняем необходимые дополнительные действия
АктуализироватьВзаиморасчеты(ТекущийОбъект);
КонецЕсли;
КонецПроцедуры
Такой подход гарантирует минимальную нагрузку на систему, так как тяжелые расчеты производятся однократно в момент сохранения данных, а не при каждом вводе символа пользователем.
Если количество реквизитов на форме фиксировано и новые поля не планируется добавлять динамически из кода, можно воспользоваться возможностями визуального редактора конфигуратора 1С.
Выполним следующие шаги:
РеквизитПриИзменении(Элемент).Ctrl и выделяем мышкой все необходимые поля ввода, флажки и переключатели, изменения которых нам нужно отслеживать.F4).ПриИзменении, нажимаем на кнопку выбора и выбираем из списка нашу созданную процедуру РеквизитПриИзменении.Теперь все выделенные элементы будут использовать один и тот же обработчик, избавляя вас от необходимости дублировать код.
Выбор конкретного решения зависит от специфики вашей бизнес-задачи:
ОбновлениеОтображения), но строго следите за отсутствием серверных вызовов в этой процедуре.