Почему ВнешнееСобытие срабатывает повторно и как это исправить?

Программист 1С v8.3 (Обычные формы)
← На главную

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

Рассмотрим типичный сценарий:

  1. Открыта форма документа, назовем ее Д1.
  2. Пользователь сканирует штрихкод. Срабатывает обработчик ВнешнееСобытие в форме Д1.
  3. В этом обработчике создается новый документ Д2, и его форма открывается, становясь активной.
  4. Пользователь сканирует следующий штрихкод. Теперь событие обрабатывается в активной форме Д2.
  5. В обработчике ВнешнееСобытие формы Д2 документ проводится и форма закрывается.
  6. Проблема: Сразу после закрытия Д2, фокус возвращается форме Д1, и в ней снова срабатывает обработчик ВнешнееСобытие от того же самого сканирования, которое закрыло Д2.

Попытка решить проблему простой проверкой Если Не ВводДоступен() Тогда Возврат; КонецЕсли; не приносит результата, так как к моменту, когда событие доходит до формы Д1, она уже стала активной, и функция ВводДоступен() возвращает Истина.

Анализ проблемы: почему так происходит?

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

Далее система начинает последовательно оповещать об этом событии все открытые формы. Происходит обход форм по очереди. Именно эта последовательность и создает нашу проблему. Проанализируем ситуацию по шагам:

  1. Сканер отправляет данные. Событие помещается в очередь.
  2. Система начинает обход форм. Активной является форма Д2.
  3. Срабатывает обработчик ВнешнееСобытие в форме Д2. Внутри него выполняется код, который проводит и закрывает форму Д2.
  4. В момент закрытия формы Д2, операционная система автоматически передает фокус и активность предыдущему окну — форме Д1.
  5. Обход событий платформой 1С продолжается. Следующей в очереди оказывается форма Д1.
  6. Система вызывает обработчик ВнешнееСобытие для формы Д1. На этот момент Д1 уже активна, поэтому проверка ВводДоступен() успешно проходит, и код обработчика выполняется, хотя не должен был.

Таким образом, проблема не в ошибке функции ВводДоступен(), а в том, что состояние активности форм меняется прямо в процессе обработки одного и того же события.

Решение 1: Быстрый способ через ОбработчикОжидания

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

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

Посмотрим на пример:

Вместо того чтобы писать в обработчике ВнешнееСобытие формы Д2:


Процедура ОбработкаВнешнегоСобытия(Источник, Событие, Данные)
    // ... какой-то код обработки ...
    ЭтотОбъект.Записать(РежимЗаписиДокумента.Проведение);
    Закрыть();
КонецПроцедуры

Мы выносим закрытие в отдельную процедуру и подключаем ее через однократный обработчик ожидания.


Процедура ОбработкаВнешнегоСобытия(Источник, Событие, Данные)
    // ... какой-то код обработки ...
    ЭтотОбъект.Записать(РежимЗаписиДокумента.Проведение);
    
    // Подключаем однократный обработчик, который сработает через 0.1 секунды
    ПодключитьОбработчикОжидания("ЗакрытьФормуПослеСобытия", 0.1, Истина);
КонецПроцедуры

Процедура ЗакрытьФормуПослеСобытия() Экспорт
    Закрыть();
КонецПроцедуры

Как это работает:

Процедура ОбработкаВнешнегоСобытия завершается почти мгновенно, не закрывая форму. Внешнее событие продолжает свой путь по другим формам (включая Д1), но так как они неактивны, их обработчики ничего не делают. И только после того, как событие полностью обработано системой, через 0.1 секунды срабатывает наш обработчик ожидания и спокойно закрывает форму Д2. Повторного вызова в Д1 уже не происходит.

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

Решение 2: Системный подход с использованием глобального флага

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

Алгоритм действий:

  1. В общем клиентском модуле (или модуле приложения) создаем экспортную переменную, например, глСобытиеСканераОбработано.
  2. В каждой форме, которая может обработать событие, в самом начале процедуры ВнешнееСобытие добавляем проверку этого флага. Если он установлен в Истина, значит, кто-то другой уже взял событие в работу, и мы просто выходим из процедуры.
  3. Та форма, которая успешно обрабатывает событие (в нашем случае Д2), после проверки устанавливает этот флаг в Истина.
  4. Важный момент: флаг нужно сбросить обратно в Ложь, чтобы система была готова к следующему сканированию. Делать это нужно также асинхронно, с помощью обработчика ожидания, чтобы сброс произошел уже после того, как событие обошло все формы.

Пример реализации:

В общем модуле (например, РаботаСоСканеромКлиент):


Перем глСобытиеСканераОбработано Экспорт;

Процедура ИнициализироватьПеременные() Экспорт
    глСобытиеСканераОбработано = Ложь;
КонецПроцедуры

Процедура СброситьФлагОбработкиСобытия() Экспорт
    глСобытиеСканераОбработано = Ложь;
КонецПроцедуры

Не забудьте вызвать РаботаСоСканеромКлиент.ИнициализироватьПеременные() при старте системы.

В обработчике ВнешнееСобытие формы Д2 (и всех других форм):


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

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

Решение 3: Архитектурно правильный подход – централизованный обработчик

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

Концепция такова:

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

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

Важные рекомендации

Независимо от выбранного способа решения, всегда придерживайтесь важного правила:

Всегда анализируйте параметры события. Процедура ОбработкаВнешнегоСобытия получает три параметра: Источник, Событие и Данные. В самом начале обработчика всегда проверяйте, что событие пришло от нужного вам источника (например, Источник = "СканерШтрихкода") и что данные соответствуют ожидаемому формату. Это защитит вашу форму от реакции на "чужие" события, например, от весов или другого оборудования.

← На главную