Почему ВводДоступен() некорректно работает в обычных формах 1С и как решить проблему дублирования событий?

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

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

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

Выясняем причину: почему ломается ВводДоступен()?

В платформе 1С при считывании данных торговым оборудованием (сканером штрихкода или терминалом сбора данных) драйвер генерирует внешнее событие. Платформа рассылает это событие во все открытые формы, в модулях которых присутствует предопределенная процедура ВнешнееСобытие().

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


Процедура ВнешнееСобытие(Источник, Событие, Данные)
    Если Не ВводДоступен() Тогда
        Возврат;
    КонецЕсли;
    
    // Код обработки штрихкода
КонецПроцедуры

Ожидается, что при открытии модального дочернего окна родительская форма теряет фокус ввода, и метод ВводДоступен() в её контексте возвращает Ложь. Однако в современных релизах платформы 1С 8.3 (начиная с веток 8.3.13 / 8.3.14 и новее) из-за глобальной переработки фокуса, асинхронности и постепенного отказа от поддержки обычных форм этот механизм перестал работать корректно. Платформа ошибочно считает ввод доступным в обеих формах, из-за чего один и тот же штрихкод считывается и обрабатывается параллельно в двух местах.

Ловушка отладчика

Проанализируем важный нюанс: почему эту проблему невозможно исследовать с помощью стандартной точки останова в Конфигураторе? Дело в том, что в момент срабатывания точки останова фокус операционной системы переключается на окно Конфигуратора. Так как клиентское приложение 1С теряет фокус ввода, вызов метода ВводДоступен() в табло отладчика всегда будет возвращать Ложь.

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


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

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

---

Способ 1. Использование локального флага доступности в родительской форме

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

Рассмотрим, как реализовать этот подход по шагам:

Шаг 1. Объявим переменную в самом начале модуля родительской формы:

Перем СканированиеРазрешено;

Шаг 2. Инициализируем эту переменную при открытии формы:


Процедура ПриОткрытии()
    СканированиеРазрешено = Истина;
КонецПроцедуры

Шаг 3. В обработчике внешнего события родительской формы добавим проверку нашего флага:


Процедура ВнешнееСобытие(Источник, Событие, Данные)
    Если Не СканированиеРазрешено Тогда
        Возврат;
    КонецЕсли;
    
    Если Не ВводДоступен() Тогда
        Возврат;
    КонецЕсли;
    
    // Логика обработки штрихкода
КонецПроцедуры

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


Процедура ОткрытьМодальнуюФорму()
    СканированиеРазрешено = Ложь;
    
    // Открытие модального окна
    ОткрытьФормуМодально("Обработка.ВводДанных.Форма.ФормаВвода");
    
    СканированиеРазрешено = Истина;
КонецПроцедуры

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

---

Способ 2. Принудительное отключение оборудования

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

Разберем пример реализации этого метода:


Процедура ОткрытьМодальноеОкноОборудования()
    // Отключаем обработку событий сканера для текущей формы
    Попытка
        ПолучитьСерверТО().ОтключитьКлиента(ЭтаФорма);
    Исключение
        // Обработка потенциальных ошибок работы с сервером ТО
    КонецПопытки;

    // Открываем дочернюю форму
    ОткрытьФормуМодально("Справочник.Номенклатура.ФормаВыбора");

    // После закрытия модального окна подключаем сканер обратно
    Попытка
        ПолучитьСерверТО().ПодключитьКлиента(ЭтаФорма);
    Исключение
        // Обработка ошибок подключения
    КонецПопытки;
КонецПроцедуры

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

---

Способ 3. Глобальный флаг «СобытиеОбработано»

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

Для этого в глобальном модуле (или в модуле обычного приложения) создается глобальная переменная глТекущееСобытиеОбработано. Алгоритм работы строится на следующих правилах:

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

Рассмотрим код обработчика ожидания для сброса флага:


// В модуле формы
Процедура ВнешнееСобытие(Источник, Событие, Данные)
    Если глТекущееСобытиеОбработано Тогда
        Возврат;
    КонецЕсли;
    
    // Фиксируем, что событие взято в работу
    глТекущееСобытиеОбработано = Истина;
    ПодключитьОбработчикОжидания("СброситьФлагОбработкиСобытия", 0.1, Истина);
    
    // Выполнение полезного действия
КонецПроцедуры

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

---

Способ 4. Использование отложенной обработки события через обработчик ожидания

Этот метод основан на разрыве синхронности выполнения кода. Проблема одновременного срабатывания часто кроется в том, что платформа рассылает сообщения «веерно» в один микромомент времени, когда состояние интерфейса еще физически перерисовывается ОС.

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

Посмотрим на пример реализации отложенного вызова:


Перем БуферДанныхСканера;

Процедура ВнешнееСобытие(Источник, Событие, Данные)
    // Сохраняем пришедшие данные во временный буфер формы
    БуферДанныхСканера = Новый Структура("Источник, Событие, Данные", Источник, Событие, Данные);
    
    // Подключаем однократный обработчик с задержкой 0.1 сек
    ПодключитьОбработчикОжидания("ОтложеннаяОбработкаСобытия", 0.1, Истина);
КонецПроцедуры

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

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

← На главную