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