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

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

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

Сложность заключается в том, что основное событие элемента формы ПриИзменении срабатывает в тот момент, когда новое значение уже записано в связанный реквизит формы. Прямого стандартного свойства вроде "СтароеЗначение" у элемента формы не существует. В этой статье мы подробно разберем, как элегантно решить эту проблему, рассмотрим классические подходы и изучим современные масштабируемые паттерны программирования в управляемом приложении.

Метод 1. Классический подход: выделенные реквизиты формы

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

Разберем этот метод по шагам:

  1. В редакторе управляемой формы добавим новый реквизит. Например, если наше основное поле связано с реквизитом Контрагент, создадим вспомогательный реквизит КонтрагентСтарый с тем же типом данных.
  2. В обработчике события формы ПриОткрытии на клиенте инициализируем вспомогательный реквизит текущим значением:

&НаКлиенте
Процедура ПриОткрытии(Отказ)
    КонтрагентСтарый = Объект.Контрагент;
КонецПроцедуры
  1. В обработчике события ПриИзменении для поля ввода выполним сравнение значений, выполним бизнес-логику и обновим кэш:

&НаКлиенте
Процедура КонтрагентПриИзменении(Элемент)
    
    // Проверяем, изменилось ли значение на самом деле
    Если Объект.Контрагент <> КонтрагентСтарый Тогда
        
        // Выполняем алгоритм (например, очищаем договор)
        Объект.ДоговорКонтрагента = ПредопределенноеЗначение("Справочник.ДоговорыКонтрагентов.ПустаяСсылка");
        
        // Перезаписываем старое значение актуальным для последующих изменений
        КонтрагентСтарый = Объект.Контрагент;
        
    КонецЕсли;
    
КонецПроцедуры

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

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

Метод 2. Использование единой коллекции-соответствия в модуле формы

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

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

Выясним, как реализовать этот алгоритм:

  1. В самом верху модуля формы объявим переменную:

Перем СоответствиеСтарыхЗначений;

  1. В процедуре ПриОткрытии инициализируем ее объектом Соответствие и заполним текущими значениями нужных полей:

&НаКлиенте
Процедура ПриОткрытии(Отказ)
    
    СоответствиеСтарыхЗначений = Новый Соответствие;
    СоответствиеСтарыхЗначений.Вставить("Контрагент", Объект.Контрагент);
    СоответствиеСтарыхЗначений.Вставить("Организация", Объект.Организация);
    СоответствиеСтарыхЗначений.Вставить("Склад", Объект.Склад);
    
КонецПроцедуры
  1. Создадим единый или индивидуальный обработчик события ПриИзменении. Обратите внимание, что мы можем использовать параметр Элемент.Имя, чтобы сделать код максимально универсальным:

&НаКлиенте
Процедура ПолеПриИзменении(Элемент)
    
    ИмяПоля = Элемент.Имя; // Имя элемента должно совпадать с именем реквизита
    НовоеЗначение = Объект[ИмяПоля];
    СтароеЗначение = СоответствиеСтарыхЗначений.Получить(ИмяПоля);
    
    Если СтароеЗначение <> НовоеЗначение Тогда
        
        // Наш общий алгоритм обработки изменений
        ПоказатьПредупреждение(, "Значение поля '" + ИмяПоля + "' изменено с '" + СтароеЗначение + "' на '" + НовоеЗначение + "'!");
        
        // Обновляем значение в кэше соответствия
        СоответствиеСтарыхЗначений.Вставить(ИмяПоля, НовоеЗначение);
        
    КонецЕсли;
    
КонецПроцедуры

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

Метод 3. Перехват в событии «ОбработкаВыбора»

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

Для этого мы можем использовать событие элемента формы ОбработкаВыбора. Посмотрим на этот механизм по шагам:

Рассмотрим пример реализации сценария, при котором мы запрашиваем подтверждение пользователя при смене значения:


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

&НаКлиенте
Процедура ПослеПодтвержденияСменыКонтрагента(Результат, ДополнительныеПараметры) Экспорт
    
    Если Результат = КодВозвратаДиалога.Да Тогда
        // Записываем новое значение вручную, так как стандартную обработку мы отключили
        Объект.Contragent = ДополнительныеПараметры.НовоеЗначение;
        // Очищаем зависимые данные
        Объект.Товары.Очистить();
    КонецЕсли;
    
КонецПроцедуры

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

Метод 4. Получение старого значения напрямую из базы данных

Если редактируемое поле связано с реквизитом объекта базы данных (например, документа или элемента справочника), который уже был ранее записан в ИБ, нам совсем не обязательно хранить кэш на клиенте. Мы можем в любой момент обратиться к базе данных на сервере и получить сохраненное (старое) значение.

Рассмотрим, как это сделать наиболее производительным способом. Прямое обращение через точку к реквизитам ссылки (например, Объект.Ссылка.Контрагент) на сервере заставляет систему считывать весь объект целиком из базы данных или кэша, что может негативно сказаться на быстродействии. Правильнее использовать легкий точечный запрос:


&НаСервереБезКонтекста
Функция ПолучитьСтароеЗначениеРеквизита(СсылкаНаОбъект)
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Договор.Контрагент КАК Контрагент
        |ИЗ
        |   Справочник.ДоговорыКонтрагентов КАК Договор
        |ГДЕ
        |   Договор.Ссылка = &Ссылка";
        
    Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект);
    Выборка = Запрос.Выполнить().Выбрать();
    
    Если Выборка.Следующий() Тогда
        Возврат Выборка.Контрагент;
    КонецЕсли;
    
    Возврат Справочники.Контрагенты.ПустаяСсылка();
    
КонецФункции

Этот способ идеально подходит для серверных проверок при записи объекта или выполнении сложных транзакционных расчетов.

Метод 5. Специфика работы с табличными частями (ТЧ)

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

Проанализируем пример кэширования колонки Количество в табличной части:

  1. Создадим переменную модуля для временного хранения структуры строки:

Перем КэшСтрокиТаблицы;

  1. В событии таблицы ПриАктивизацииСтроки будем фиксировать текущие значения активной строки:

&НаКлиенте
Процедура ТоварыПриАктивизацииСтроки(Элемент)
    
    ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
    Если ТекущиеДанные <> Неопределено Тогда
        КэшСтрокиТаблицы = Новый Структура;
        КэшСтрокиТаблицы.Вставить("ИндексСтроки", ТекущиеДанные.ПолучитьИдентификатор());
        КэшСтрокиТаблицы.Вставить("Количество", ТекущиеДанные.Количество);
    Иначе
        КэшСтрокиТаблицы = Неопределено;
    КонецЕсли;
    
КонецПроцедуры
  1. В событии изменения поля внутри строки выполним сравнение с кэшем:

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

Резюме

Мы подробно разобрали пять различных методов получения предыдущего значения поля формы. Выбор конкретного инструмента зависит от ваших задач:

← На главную