Как устранить двойное срабатывание события ПриАктивизацииСтроки при установке отбора в 1С?

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

При разработке сложных интерфейсов на управляемых формах 1С программисты часто сталкиваются с неприятным эффектом: при выборе строки в одной таблице событие ПриАктивизацииСтроки вызывается дважды (а иногда и трижды). Это приводит к лишним серверным вызовам, «миганию» интерфейса и некорректной работе алгоритмов, особенно если в обработчиках используются ресурсоемкие запросы или сложные виды сравнения, такие как ВидСравненияКомпоновкиДанных.ВИерархии или ВидСравненияКомпоновкиДанных.ВСписке.

В этой статье мы подробно разберем причины возникновения этой проблемы, проанализируем физику поведения платформы «1С:Предприятие 8.3» и рассмотрим несколько надежных способов устранения двойного срабатывания. Для автоматизации обслуживания и тестирования исправленных баз удобно использовать программу для обновления 1С в «тихом» режиме.

Почему происходит двойное срабатывание события?

Для начала выясним причину такого поведения платформы. Событие ПриАктивизацииСтроки генерируется каждый раз, когда фокус в таблице перемещается на другую строку. Однако при программной установке отборов в динамическом списке запускается цепная реакция:

  1. Пользователь активирует строку в первой таблице (например, «Виды номенклатуры»).
  2. Срабатывает событие ПриАктивизацииСтроки, код которого накладывает новый отбор на вторую таблицу (например, «Номенклатура»).
  3. Платформа отправляет запрос к базе данных для обновления состава строк второй таблицы.
  4. В процессе обновления данных фокус во второй таблице временно «теряется» (так как старый набор строк уничтожается) и затем устанавливается заново на первую доступную строку обновленного списка. Это перепозиционирование вызывает повторный вызов события ПриАктивизацииСтроки для второй таблицы.
  5. Если во второй таблице также написан обработчик, который пытается как-то повлиять на состояние первой таблицы (или формы в целом), происходит повторная инициализация событий, что создает иллюзию «двойного клика» или циклического вызова.

Особую сложность представляют отборы с видом сравнения В иерархии. Этот отбор требует от платформы построения сложного дерева зависимостей на сервере СУБД. Время отклика увеличивается, и фокус в таблице гарантированно перерисовывается, что практически всегда провоцирует повторную генерацию события активации на клиенте. Чтобы снизить нагрузку на сервер при работе с большими базами ERP, можно провести многопоточную свёртку 1С:ERP для оптимизации состава данных.


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

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

Проанализируем ситуацию на примере связанного списка номенклатуры. Вместо того чтобы программно создавать элементы отбора, мы внесем изменения непосредственно в текст запроса динамического списка СписокНоменклатура.

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


ВЫБРАТЬ
    СправочникНоменклатура.Ссылка КАК Ссылка,
    СправочникНоменклатура.Наименование КАК Наименование,
    СправочникНоменклатура.ВидНоменклатуры КАК ВидНоменклатуры,
    СправочникНоменклатура.ЭтоГруппа КАК ЭтоГруппа
ИЗ
    Справочник.Номенклатура КАК СправочникНоменклатура
ГДЕ
    &ВидНоменклатуры = ЗНАЧЕНИЕ(Справочник.ВидыНоменклатуры.ПустаяСсылка)
    ИЛИ СправочникНоменклатура.ВидНоменклатуры В ИЕРАРХИИ (&ВидНоменклатуры)

Шаг 2. В обработчике активизации строки таблицы «Виды номенклатуры» будем просто устанавливать значение этого параметра. Это работает значительно быстрее и стабильнее, исключая лишние перестроения схемы компоновки данных:


&НаКлиенте
Процедура ВидыНоменклатурыПриАктивизацииСтроки(Элемент)
    
    ТекущиеДанные = Элементы.ВидыНоменклатуры.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда
        ЗначениеПараметра = ПредопределенноеЗначение("Справочник.ВидыНоменклатуры.ПустаяСсылка");
    Иначе
        ЗначениеПараметра = ТекущиеДанные.Ссылка;
    КонецЕсли;
    
    // Устанавливаем параметр динамического списка напрямую
    СписокНоменклатура.Параметры.УстановитьЗначениеПараметра("ВидНоменклатуры", ЗначениеПараметра);
    
КонецПроцедуры

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


Способ 2. Защита от дребезга контактов (Использование обработчика ожидания)

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

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

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


&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
    
    // Отключаем предыдущий обработчик, если он уже был запущен
    ОтключитьОбработчикОжидания("ВыполнитьОтложеннуюАктивизациюСтроки");
    
    // Подключаем однократный обработчик с минимальной задержкой
    ПодключитьОбработчикОжидания("ВыполнитьОтложеннуюАктивизациюСтроки", 0.1, Истина);
    
КонецПроцедуры

&НаКлиенте
Процедура ВыполнитьОтложеннуюАктивизациюСтроки()
    
    ТекущиеДанные = Элементы.Список.ТекущиеДанные;
    Если ТекущиеДанные = Неопределено Тогда
        Возврат;
    КонецЕсли;
    
    // Здесь мы выполняем весь тяжелый код: серверные вызовы, установку отборов и т.д.
    ВыполнитьОбновлениеСвязанныхДанныхНаСервере(ТекущиеДанные.Ссылка);
    
КонецПроцедуры

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


Способ 3. Блокировка повторного входа через кэширование идентификатора строки

Еще одна частая причина повторного срабатывания — ситуация, когда платформа при обновлении списка сначала принудительно активирует пустую строку (или сбрасывает фокус на первую), а затем возвращает его обратно на исходную позицию. Чтобы отсечь такие «ложные» срабатывания, мы можем сохранять идентификатор последней обработанной строки в переменной модуля формы.

Разберем, как реализовать этот алгоритм:


&НаКлиенте
Переменная ПоследнийОбработанныйИдентификатор;

&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
    
    ТекущаяСтрока = Элемент.ТекущаяСтрока;
    
    // Если идентификатор строки не изменился, просто выходим из процедуры
    Если ТекущаяСтрока = ПоследнийОбработанныйИдентификатор Тогда
        Возврат;
    КонецЕсли;
    
    // Запоминаем текущий идентификатор для последующих сравнений
    ПоследнийОбработанныйИдентификатор = ТекущаяСтрока;
    
    // Выполняем полезную нагрузку
    ПрименитьОтборПоСтроке(ТекущаяСтрока);
    
КонецПроцедуры

Важное примечание: Переменная ПоследнийОбработанныйИдентификатор должна быть объявлена в самом верху модуля формы с директивой &НаКлиенте, чтобы сохранять свое значение между вызовами процедур.


Способ 4. Устранение циклической связи в типовых механизмах (на примере УТ 11 / ERP)

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

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

Проанализируем пример безопасного изменения отбора без вызова побочных эффектов синхронизации:


&НаСервереБезКонтекста
Процедура УстановитьБезопасныйОтбор(НастройкиКомпоновки, ЗначениеОтбора)
    
    // Находим нужный элемент отбора в фиксированных настройках
    ЭлементОтбора = Неопределено;
    Для Каждого Элемент Из НастройкиКомпоновки.Отбор.Элементы Цикл
        Если Строка(Элемент.ЛевоеЗначение) = "ВидНоменклатуры" Тогда
            ЭлементОтбора = Элемент;
            Прервать;
        КонецЕсли;
    КонецЦикла;
    
    Если ЭлементОтбора = Неопределено Тогда
        ЭлементОтбора = НастройкиКомпоновки.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
        ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ВидНоменклатуры");
    КонецЕсли;
    
    // Вместо "Равно" используем "В иерархии" только при реальной необходимости
    Если ЗначениеЗаполнено(ЗначениеОтбора) Тогда
        ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.ВИерархии;
        ЭлементОтбора.ПравоеЗначение = ЗначениеОтбора;
        ЭлементОтбора.Использование = Истина;
    Иначе
        ЭлементОтбора.Использование = Ложь;
    EndIf;
    
КонецПроцедуры

Использование контекстных вызовов &НаСервереБезКонтекста при обновлении настроек отборов позволяет избежать передачи всего состояния формы туда и обратно. Это исключает непроизвольные сбросы фокуса и сопутствующую им генерацию лишних клиентских событий ПриАктивизацииСтроки.

Резюме и рекомендации

Для создания быстрого и отзывчивого интерфейса придерживайтесь следующих правил:

  1. Избегайте серверных вызовов внутри события ПриАктивизацииСтроки. Если серверный вызов необходим, делайте его контекстно независимым (&НаСервереБезКонтекста) или откладывайте его выполнение.
  2. Отдавайте приоритет параметрам динамического списка перед программным добавлением элементов отбора в компоновщик.
  3. Всегда используйте «защиту от дребезга» через ПодключитьОбработчикОжидания для тяжелых операций интерактивного интерфейса.
  4. Контролируйте уникальность активной строки с помощью кэширования её идентификатора на клиенте.
← На главную