При работе с торговым оборудованием, например, сканером штрихкодов, в 1С на обычных формах можно столкнуться с неожиданным поведением. Событие от сканера, которое должно быть обработано только в одной, активной форме, после ее закрытия вдруг "прилетает" в предыдущую, ставшую активной форму. Это может вызывать нежелательные повторные действия и ошибки в логике программы. Давайте разберемся, почему так происходит и какие существуют способы решения этой проблемы.
Рассмотрим типичный сценарий:
ВнешнееСобытие в форме Д1.ВнешнееСобытие формы Д2 документ проводится и форма закрывается.ВнешнееСобытие от того же самого сканирования, которое закрыло Д2.Попытка решить проблему простой проверкой Если Не ВводДоступен() Тогда Возврат; КонецЕсли; не приносит результата, так как к моменту, когда событие доходит до формы Д1, она уже стала активной, и функция ВводДоступен() возвращает Истина.
Чтобы найти правильное решение, нужно понять механизм распространения внешних событий в платформе 1С. Ключевой момент заключается в том, что внешнее событие от оборудования (сканера, считывателя магнитных карт и т.д.) поступает в единую очередь сообщений приложения.
Далее система начинает последовательно оповещать об этом событии все открытые формы. Происходит обход форм по очереди. Именно эта последовательность и создает нашу проблему. Проанализируем ситуацию по шагам:
ВнешнееСобытие в форме Д2. Внутри него выполняется код, который проводит и закрывает форму Д2.ВнешнееСобытие для формы Д1. На этот момент Д1 уже активна, поэтому проверка ВводДоступен() успешно проходит, и код обработчика выполняется, хотя не должен был.Таким образом, проблема не в ошибке функции ВводДоступен(), а в том, что состояние активности форм меняется прямо в процессе обработки одного и того же события.
Самый простой и быстрый способ разорвать эту "порочную" цепочку событий — использовать асинхронный вызов, отложив действие, меняющее активную форму (в нашем случае — закрытие). Для этого идеально подходит механизм обработчиков ожидания.
Идея в том, чтобы не закрывать форму Д2 прямо в процедуре ВнешнееСобытие, а лишь запланировать ее закрытие через очень короткий промежуток времени. Этого времени будет достаточно, чтобы исходное внешнее событие успело "пройти" по всем остальным формам, которые на тот момент еще неактивны, и "погаснуть".
Посмотрим на пример:
Вместо того чтобы писать в обработчике ВнешнееСобытие формы Д2:
Процедура ОбработкаВнешнегоСобытия(Источник, Событие, Данные)
// ... какой-то код обработки ...
ЭтотОбъект.Записать(РежимЗаписиДокумента.Проведение);
Закрыть();
КонецПроцедуры
Мы выносим закрытие в отдельную процедуру и подключаем ее через однократный обработчик ожидания.
Процедура ОбработкаВнешнегоСобытия(Источник, Событие, Данные)
// ... какой-то код обработки ...
ЭтотОбъект.Записать(РежимЗаписиДокумента.Проведение);
// Подключаем однократный обработчик, который сработает через 0.1 секунды
ПодключитьОбработчикОжидания("ЗакрытьФормуПослеСобытия", 0.1, Истина);
КонецПроцедуры
Процедура ЗакрытьФормуПослеСобытия() Экспорт
Закрыть();
КонецПроцедуры
Как это работает:
Процедура ОбработкаВнешнегоСобытия завершается почти мгновенно, не закрывая форму. Внешнее событие продолжает свой путь по другим формам (включая Д1), но так как они неактивны, их обработчики ничего не делают. И только после того, как событие полностью обработано системой, через 0.1 секунды срабатывает наш обработчик ожидания и спокойно закрывает форму Д2. Повторного вызова в Д1 уже не происходит.
Этот метод является рабочим, но многие разработчики считают его "костылем", так как он основан на задержке и не решает проблему архитектурно. Однако для быстрых исправлений он отлично подходит.
Более надежный и управляемый способ — ввести глобальный флаг, который будет сигнализировать, что событие уже было обработано. Этот подход требует немного больше кода, но делает логику более прозрачной.
Алгоритм действий:
глСобытиеСканераОбработано.ВнешнееСобытие добавляем проверку этого флага. Если он установлен в Истина, значит, кто-то другой уже взял событие в работу, и мы просто выходим из процедуры.Истина.Ложь, чтобы система была готова к следующему сканированию. Делать это нужно также асинхронно, с помощью обработчика ожидания, чтобы сброс произошел уже после того, как событие обошло все формы.Пример реализации:
В общем модуле (например, РаботаСоСканеромКлиент):
Перем глСобытиеСканераОбработано Экспорт;
Процедура ИнициализироватьПеременные() Экспорт
глСобытиеСканераОбработано = Ложь;
КонецПроцедуры
Процедура СброситьФлагОбработкиСобытия() Экспорт
глСобытиеСканераОбработано = Ложь;
КонецПроцедуры
Не забудьте вызвать РаботаСоСканеромКлиент.ИнициализироватьПеременные() при старте системы.
В обработчике ВнешнееСобытие формы Д2 (и всех других форм):
Процедура ОбработкаВнешнегоСобытия(Источник, Событие, Данные)
// Шаг 1: Проверяем, не обработано ли событие уже
Если РаботаСоСканеромКлиент.глСобытиеСканераОбработано Тогда
Возврат;
КонецЕсли;
// Проверяем, что это наше событие и мы его будем обрабатывать
Если Источник = "СканерШтрихкода" И ЭтоНужныеНамДанные(Данные) Тогда
// Шаг 2: Устанавливаем флаг, что мы взяли событие в работу
РаботаСоСканеромКлиент.глСобытиеСканераОбработано = Истина;
// Шаг 3: Планируем сброс флага для следующего сканирования
ПодключитьОбработчикОжидания("РаботаСоСканеромКлиент.СброситьФлагОбработкиСобытия", 0.1, Истина);
// ... Основная логика обработки штрихкода ...
ЭтотОбъект.Записать(РежимЗаписиДокумента.Проведение);
Закрыть();
КонецЕсли;
КонецПроцедуры
Этот подход более управляем и не зависит от задержек при закрытии форм, что делает его предпочтительнее первого.
Наиболее правильным, особенно для сложных конфигураций, является создание централизованной системы управления внешними событиями. Вместо того чтобы каждая форма "слушала" все подряд, мы создаем единый диспетчер.
Концепция такова:
ОбработкаВнешнегоСобытия размещается не в формах, а в модуле приложения.Этот метод требует большего проектирования, но обеспечивает полный контроль над потоком событий, исключая любые случайные срабатывания. Он является лучшей практикой для разработки масштабируемых систем.
Независимо от выбранного способа решения, всегда придерживайтесь важного правила:
Всегда анализируйте параметры события. Процедура ОбработкаВнешнегоСобытия получает три параметра: Источник, Событие и Данные. В самом начале обработчика всегда проверяйте, что событие пришло от нужного вам источника (например, Источник = "СканерШтрихкода") и что данные соответствуют ожидаемому формату. Это защитит вашу форму от реакции на "чужие" события, например, от весов или другого оборудования.