При разработке интерфейсов в "1С:Предприятии" мы часто сталкиваемся с необходимостью контролировать действия пользователя. Типичная задача: отследить изменение числового показателя (например, количества или цены в табличной части) и сравнить новое введенное значение со старым, чтобы выполнить определенные расчеты, перезаполнить зависимые поля или заблокировать некорректный ввод. Однако начинающие разработчики часто сталкиваются с тем, что привычное событие ОбработкаВыбора ведет себя, на первый взгляд, непредсказуемо — при использовании встроенного калькулятора оно срабатывает, а при обычном наборе цифр с клавиатуры полностью игнорируется.
В этой статье мы подробно разберем, почему так происходит, изучим внутреннюю логику работы платформы "1С:Предприятие" и рассмотрим четыре надежных практических способа решения этой задачи с готовыми примерами кода.
Для начала давайте проанализируем поведение платформы и разберемся, почему разработчики часто совершают архитектурную ошибку, полагаясь на событие ОбработкаВыбора (ChoiceProcessing). С точки зрения платформы 1С, это событие предназначено исключительно для обработки возврата значения из внешнего источника. Таким источником может быть:
Когда пользователь нажимает кнопку выбора в ячейке (три точки или иконку калькулятора), платформа открывает внешнюю форму. После того как пользователь нажимает кнопку "ОК" или выбирает число, это значение возвращается в исходную форму. В этот момент и генерируется событие ОбработкаВыбора.
Но что происходит, когда пользователь просто кликает по ячейке мышкой и начинает вводить число непосредственно с клавиатуры? В этом случае никакая внешняя форма не открывается. Происходит прямой интерактивный ввод символов в элемент управления на форме. Платформа фиксирует изменение текста, а при выходе из поля преобразует введенную строку в число и записывает его в реквизит. При таком сценарии генерируются события изменения текста и, наконец, событие ПриИзменении (OnChange). Никакого "выбора" здесь физически не происходит, а значит, событие ОбработкаВыбора законно игнорируется. Это не баг и не косяк платформы, а строго задокументированная логика работы клиентского приложения.
Таким образом, для решения нашей задачи нам необходимо использовать другие механизмы. Давайте по шагам рассмотрим наиболее эффективные методы сравнения текущего (нового) и предыдущего (старого) значений.
Это самый популярный, гибкий и надежный способ, который подходит как для новых, так и для уже записанных документов. Суть метода заключается в том, что мы создаем временный "буфер" на форме, в котором будем хранить значение ячейки до момента ее изменения.
В обсуждениях на форумах часто предлагают простую схему: записывать старое значение в событии ПриАктивизацииСтроки, а проверять в событии ПриИзменении. Однако в этой схеме кроется серьезная логическая уязвимость. Представьте сценарий:
10. Срабатывает событие ПриАктивизацииСтроки, мы записываем в буфер число 10.20. Срабатывает событие ПриИзменении. Мы сравниваем 20 и 10 (все отлично!).30. Поскольку строка не менялась, событие ПриАктивизацииСтроки повторно не вызывается. В нашем буфере по-прежнему хранится число 10!ПриИзменении. Программа сравнивает новое значение 30 со значением из буфера 10, хотя реальное предыдущее значение в ячейке было 20. Наша бизнес-логика отрабатывает некорректно.Чтобы избежать этой ошибки, мы должны своевременно обновлять наш буфер прямо в момент изменения данных. Давайте реализуем этот алгоритм правильно.
Шаг 1. Добавим в реквизиты нашей управляемой формы новый скрытый реквизит. Назовем его СтароеЗначениеКоличества (тип — Число).
Шаг 2. Создадим обработчик события ПриАктивизацииСтроки (или ПриАктивизацииЯчейки для большей точности) для нашей табличной части. Нам нужно запомнить значение в буфер при каждом переходе пользователя на новую строку:
&НаКлиенте
Процедура ТоварыПриАктивизацииСтроки(Элемент)
ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
Если ТекущиеДанные <> Неопределено Тогда
// Запоминаем текущее значение ячейки в реквизит формы
СтароеЗначениеКоличества = ТекущиеДанные.Количество;
Иначе
СтароеЗначениеКоличества = 0;
КонецЕсли;
КонецПроцедуры
Шаг 3. Создадим обработчик события ПриИзменении для нашего числового поля ввода (колонки "Количество"). Здесь мы выполняем сравнение, запускаем нашу логику и — что критически важно — перезаписываем буфер новым значением:
&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
НовоеЗначение = ТекущиеДанные.Количество;
// Сравниваем новое значение со старым из буфера
Если НовоеЗначение <> СтароеЗначениеКоличества Тогда
// Здесь описываем вашу бизнес-логику
Разница = НовоеЗначение - СтароеЗначениеКоличества;
ПоказатьОповещениеПользователя("Изменение количества", ,
СтроковыеФункцииКлиентСервер.Шаблон("Количество изменилось на %1 шт.", Разница));
// КРИТИЧЕСКИ ВАЖНО: обновляем буфер текущим значением!
// Это спасет нас при повторном изменении в этой же строке без смены фокуса.
СтароеЗначениеКоличества = НовоеЗначение;
КонецЕсли;
КонецПроцедуры
Этот подход является стандартным, работает абсолютно стабильно и не нагружает систему лишними серверными вызовами.
Если редактируемый объект (например, документ или элемент справочника) уже был ранее записан в информационную базу, мы можем обойтись без временных реквизитов формы. Мы можем сравнить текущие данные на форме с теми данными, которые физически хранятся в СУБД.
Этот метод идеально подходит для тех случаев, когда нам нужно жестко контролировать отклонения от первоначально утвержденных значений. Рассмотрим пример реализации:
&НаСервереБезКонтекста
Функция ПолучитьСтароеКоличествоИзБазы(СсылкаНаДокумент, ИдентификаторСтроки)
// Если документ новый и еще не записан, то ссылки нет
Если Не ЗначениеЗаполнено(СсылкаНаДокумент) Тогда
Возврат 0;
КонецЕсли;
// Выполняем запрос к табличной части документа в базе данных
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Количество КАК Количество
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Товары
|ГДЕ
| Товары.Ссылка = &Ссылка
| И Товары.НомерСтроки = &НомерСтроки"; // Или поиск по другому уникальному ключу
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
// Обратите внимание: ИдентификаторСтроки на форме может не совпадать с НомеромСтроки в БД,
// поэтому надежнее использовать уникальный ключ строки, если он есть.
Запрос.УстановитьПараметр("НомерСтроки", ИдентификаторСтроки);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Количество;
КонецЕсли;
Возврат 0;
КонецФункции
&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
НовоеЗначение = ТекущиеДанные.Количество;
// Получаем первоначальное значение из БД
СтароеЗначение = ПолучитьСтароеКоличествоИзБазы(Объект.Ссылка, ТекущиеДанные.НомерСтроки);
Если НовоеЗначение <> СтароеЗначение Тогда
// Выполняем проверку или расчет
// Например, запрещаем уменьшать количество ниже зарезервированного в БД
Если НовоеЗначение < СтароеЗначение Тогда
ВызватьИсключение "Нельзя устанавливать количество меньше зарезервированного ранее!";
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Минусы метода:
Объект.Ссылка будет пустой).Для текстовых и числовых полей ввода в платформе предусмотрено специализированное событие ОкончаниеВводаТекста (TextEditEnd). Оно генерируется в тот момент, когда пользователь завершил ввод текста в поле (например, нажал клавишу Enter, Tab или кликнул мышкой вне поля ввода), но платформа еще не успела преобразовать этот текст в число и записать в реквизит объекта.
Это событие позволяет перехватить «сырое» строковое значение, введенное пользователем, и провести его предварительную валидацию. Рассмотрим пример использования:
&НаКлиенте
Процедура ТоварыКоличествоОкончаниеВводаТекста(Элемент, Текст, ДанныеВыбора, СтандартнаяОбработка)
// Переводим введенный текст в числовой формат
Попытка
ВведенноеЧисло = Число(Текст);
Исключение
// Если пользователь ввел некорректные символы
Возврат;
КонецПопытки;
ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
Если ТекущиеДанные <> Неопределено Тогда
СтароеЗначение = ТекущиеДанные.Количество;
// Сравниваем значения до того, как они изменятся в самой ТЧ
Если ВведенноеЧисло > СтароеЗначение * 2 Тогда
ПоказатьПредупреждение(, "Вы увеличили количество более чем в 2 раза! Проверьте корректность ввода.");
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Этот способ достаточно изящен, однако для стандартных числовых полей он применяется редко. Чаще всего его используют при реализации сложных механизмов автоподбора текстовой информации или кастомного разбора штрихкодов.
Бывают ситуации, когда бизнес-логика требует от нас максимальной строгости при вводе данных. Например, когда случайное изменение количества в строке может повлечь за собой серьезные ошибки в учете, и нам необходимо всегда запрашивать у пользователя подтверждение операции или предлагать специализированный диалог ввода, который будет выглядеть и работать абсолютно одинаково как при работе на ПК, так и на мобильных устройствах.
Для реализации такого жесткого контроля мы можем полностью заблокировать стандартный ввод с клавиатуры в ячейку табличной части и перехватить управление процессом ввода:
Шаг 1. Настроим свойства элемента формы (нашего поля ввода количества):
ТолькоПросмотр в значение Истина. Теперь пользователь не сможет напрямую вписывать цифры в ячейку с клавиатуры.КнопкаВыбора в значение Истина.Шаг 2. Напишем обработчик события НачалоВыбора для этого поля. В нем мы отменим стандартное поведение платформы и вызовем асинхронный метод ПоказатьВводЧисла:
&НаКлиенте
Процедура ТоварыКоличествоНачалоВыбора(Элемент, ДанныеВыбора, СтандартнаяОбработка)
// Отменяем стандартное открытие калькулятора или формы выбора
СтандартнаяОбработка = Ложь;
ТекущиеДанные = Элементы.Товары.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
Возврат;
КонецЕсли;
// Подготавливаем описание оповещения для обработки результата ввода
ПараметрыОповещения = Новый ОписаниеОповещения("ПослеВводаКоличества", ЭтотОбъект, ТекущиеДанные);
// Вызываем асинхронный диалог ввода числа
ПоказатьВводЧисла(ПараметрыОповещения, ТекущиеДанные.Количество, "Введите новое количество:");
КонецПроцедуры
Шаг 3. Создадим экспортную процедуру-колбэк, которая примет введенное пользователем значение, сравнит его с предыдущим и запишет в табличную часть при успешном прохождении проверок:
&НаКлиенте
Процедура ПослеВводаКоличества(РезультатВвода, ДополнительныеПараметры) Экспорт
// Если пользователь закрыл диалог или нажал Отмена, РезультатВвода будет равен Неопределено
Если РезультатВвода = Неопределено Тогда
Возврат;
КонецЕсли;
СтароеКоличество = ДополнительныеПараметры.Количество;
НовоеКоличество = РезультатВвода;
// Выполняем сравнение старого и нового значений
Если НовоеКоличество <> СтароеКоличество Тогда
// Пример проверки бизнес-логики: количество должно быть строго больше нуля
Если НовоеКоличество <= 0 Тогда
ПоказатьПредупреждение(, "Количество должно быть больше нуля!");
Возврат;
КонецЕсли;
// Записываем новое значение в строку табличной части
ДополнительныеПараметры.Количество = НовоеКоличество;
// Вызываем логику пересчета сумм и налогов
РассчитатьСуммуСтроки(ДополнительныеПараметры);
КонецЕсли;
КонецПроцедуры
Этот подход гарантирует 100% стабильность работы интерфейса. Пользователь гарантированно увидит красивое окно ввода числа, а логика сравнения отработает одинаково корректно независимо от того, нажал ли пользователь кнопку калькулятора или воспользовался физической клавиатурой.
Мы подробно проанализировали особенности работы платформы "1С:Предприятие" и выяснили, что событие ОбработкаВыбора не предназначено для ручного ввода данных с клавиатуры. Для качественного решения этой задачи мы рассмотрели четыре альтернативных механизма:
Выбирайте наиболее подходящий для вашей конфигурации метод и делайте ваши интерфейсы надежными и удобными для пользователей!