Как устранить зацикливание формы при вызове сервера в событии ПриАктивизацииСтроки?

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

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

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

Почему возникает зацикливание при активизации строки

Проанализируем механику работы управляемой формы в платформе 1С:Предприятие 8.3:

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

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

Выясним, как устранить проблему минимальными изменениями. Первое, что необходимо сделать — перевести серверную процедуру в директиву &НаСервереБезКонтекста. При таком вызове данные формы не передаются на сервер и не обновляются принудительно на клиенте.

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


&НаКлиенте
Перем ТекущееОСОбработанное;

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

Способ 2. Паттерн задержки (Debounce) через обработчик ожидания

Если пользователь быстро листает список стрелками клавиатуры или колесом мыши, обращение к серверу на каждой строке вызовет сильные «заикания» интерфейса и перегрузит сервер СУБД. Решим эту проблему через подключение кратковременного обработчика ожидания.

Рассмотрим, как это работает: при каждом перемещении курсора мы сбрасываем предыдущий таймер и заводим новый с задержкой в 0.2–0.3 секунды. Запрос к базе данных выполнится только тогда, когда пользователь остановится на конкретной позиции:


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

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

Оптимизация и исправление серверного запроса

Обратим внимание на частые ошибки в коде запроса к регистру бухгалтерии:

  1. Ошибка проверки списка: Конструкция Счет = &СписокСчетов недопустима для списка значений. Необходимо использовать оператор Счет В (&СписокСчетов).
  2. Передача текущей даты: Параметр &Дата = ТекущаяДата() заставляет СУБД вычислять итоги с момента последней рассчитанной точки до текущей секунды. Если нужны актуальные текущие остатки, параметр даты в виртуальной таблице следует оставить пустым: РегистрБухгалтерии.Хозрасчетный.Остатки(, ...). В этом случае платформа мгновенно прочитает уже рассчитанные итоги.
  3. Обработка результатов: Вместо выгрузки промежуточной таблицы значений Запрос.Выполнить().Выгрузить() эффективнее использовать выборку или чтение результата через Выбрать().

Посмотрим на исправленную функцию с директивой &НаСервереБезКонтекста:


&НаСервереБезКонтекста
Функция ПолучитьДанныеПоОСНаСервере(СсылкаНаОС)
    
    Результат = Новый Структура("Дт, Кт", 0, 0);
    
    Если Не ЗначениеЗаполнено(СсылкаНаОС) Тогда
        Возврат Результат;
    КонецЕсли;
    
    МассивСчетов = Новый Массив;
    МассивСчетов.Добавить(ПланыСчетов.Хозрасчетный.ОСвОрганизации);
    МассивСчетов.Добавить(ПланыСчетов.Хозрасчетный.АмортизацияОС_01);
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ХозрасчетныйОстатки.СуммаОстатокДт КАК СуммаДт,
        |   ХозрасчетныйОстатки.СуммаОстатокКт КАК СуммаКт
        |ИЗ
        |   РегистрБухгалтерии.Хозрасчетный.Остатки(
        |       ,
        |       Счет В (&МассивСчетов),
        |       ,
        |       Субконто1 = &ОС) КАК ХозрасчетныйОстатки";
        
    Запрос.УстановитьПараметр("ОС", СсылкаНаОС);
    Запрос.УстановитьПараметр("МассивСчетов", МассивСчетов);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        Если Выборка.СуммаДт <> 0 Тогда
            Результат.Дт = Выборка.СуммаДт;
        КонецЕсли;
        Если Выборка.СуммаКт <> 0 Тогда
            Результат.Кт = Выборка.СуммаКт;
        КонецЕсли;
    КонецЦикла;
    
    Возврат Результат;
    
КонецФункции

Альтернативные архитектурные решения

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

  1. Расчет по требованию: Выводить данные об остаточной стоимости не динамически в списке, а по отдельной кнопке командной панели («Показать остатки») или в карточке конкретного основного средства.
  2. Связанный динамический список: Разместить рядом с основным списком вспомогательную табличную часть с отдельным динамическим списком, у которого настроен отбор по текущей строке. Всю оптимизацию кэширования и запросов в таком случае платформа берет на себя.
← На главную