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