При разработке интерфейсов на управляемых формах часто возникает задача отображать дополнительную аналитическую информацию по текущему объекту: остатки на складах, балансовую стоимость, амортизацию или взаиморасчеты. Нередко разработчики помещают код получения данных в событие таблицы ПриАктивизацииСтроки. Однако такой подход часто приводит к неприятному эффекту: при открытии формы или перемещении курсора программа уходит в бесконечный цикл и аварийно завершает работу.
Разберем детально, почему возникает зацикливание, какие ошибки допускаются при работе с контекстом формы и запросами к регистру бухгалтерии, а также рассмотрим надежные способы реализации такого функционала.
Проанализируем механику работы управляемой формы в платформе 1С:Предприятие 8.3:
&НаСервере, то при ее вызове на сервер передается весь контекст формы (все реквизиты и состояние элементов). При возврате управления на клиент платформа синхронизирует состояние формы и выполняет ее перерисовку. Эта перерисовка генерирует повторное событие ПриАктивизацииСтроки, что приводит к бесконечной рекурсии и падению процесса из-за переполнения стека. В синтакс-помощнике прямо указано примечание: «В обработчике данного события нельзя использовать серверные методы формы с директивой компиляции &НаСервере».ЭтотОбъект.ТекущийЭлемент.ТекущиеДанные некорректно. Свойство ТекущийЭлемент указывает на любой элемент интерфейса, на котором в данный момент находится фокус ввода (например, строка поиска или кнопка). Если фокус сместился, обращение к свойству ТекущиеДанные вернет Неопределено.ТекущиеДанные.Ссылка без проверки приводит к исключительной ситуации.Выясним, как устранить проблему минимальными изменениями. Первое, что необходимо сделать — перевести серверную процедуру в директиву &НаСервереБезКонтекста. При таком вызове данные формы не передаются на сервер и не обновляются принудительно на клиенте.
Дополнительно сохраним ссылку на текущее основное средство в переменную модуля формы, чтобы исключить повторные вызовы сервера, если пользователь просто переходит между колонками одной строки:
&НаКлиенте
Перем ТекущееОСОбработанное;
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
// Безопасно получаем данные строки через параметр Элемент
СтрокаТаблицы = Элемент.ТекущиеДанные;
Если СтрокаТаблицы = Неопределено Тогда
Возврат;
КонецЕсли;
// Проверяем, изменилась ли строка
Если СтрокаТаблицы.Ссылка = ТекущееОСОбработанное Тогда
Возврат;
КонецЕсли;
ТекущееОСОбработанное = СтрокаТаблицы.Ссылка;
ДанныеПоОС = ПолучитьДанныеПоОСНаСервере(ТекущееОСОбработанное);
БалансоваяСтоимость = ДанныеПоОС.Дт;
Амортизация = ДанныеПоОС.Кт;
ОстаточнаяСтоимость = ДанныеПоОС.Дт - ДанныеПоОС.Кт;
КонецПроцедуры
Если пользователь быстро листает список стрелками клавиатуры или колесом мыши, обращение к серверу на каждой строке вызовет сильные «заикания» интерфейса и перегрузит сервер СУБД. Решим эту проблему через подключение кратковременного обработчика ожидания.
Рассмотрим, как это работает: при каждом перемещении курсора мы сбрасываем предыдущий таймер и заводим новый с задержкой в 0.2–0.3 секунды. Запрос к базе данных выполнится только тогда, когда пользователь остановится на конкретной позиции:
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
// Отключаем предыдущий вызов, если курсор быстро перемещается
ОтключитьОбработчикОжидания("ВыполнитьПолучениеДанныхОС");
// Запускаем однократный таймер
ПодключитьОбработчикОжидания("ВыполнитьПолучениеДанныхОС", 0.2, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ВыполнитьПолучениеДанныхОС()
СтрокаТаблицы = Элементы.Список.ТекущиеДанные;
Если СтрокаТаблицы = Неопределено Тогда
Возврат;
КонецЕсли;
ДанныеПоОС = ПолучитьДанныеПоОСНаСервере(СтрокаТаблицы.Ссылка);
БалансоваяСтоимость = ДанныеПоОС.Дт;
Амортизация = ДанныеПоОС.Кт;
ОстаточнаяСтоимость = ДанныеПоОС.Дт - ДанныеПоОС.Кт;
КонецПроцедуры
Обратим внимание на частые ошибки в коде запроса к регистру бухгалтерии:
Счет = &СписокСчетов недопустима для списка значений. Необходимо использовать оператор Счет В (&СписокСчетов).&Дата = ТекущаяДата() заставляет СУБД вычислять итоги с момента последней рассчитанной точки до текущей секунды. Если нужны актуальные текущие остатки, параметр даты в виртуальной таблице следует оставить пустым: РегистрБухгалтерии.Хозрасчетный.Остатки(, ...). В этом случае платформа мгновенно прочитает уже рассчитанные итоги.Запрос.Выполнить().Выгрузить() эффективнее использовать выборку или чтение результата через Выбрать().Посмотрим на исправленную функцию с директивой &НаСервереБезКонтекста:
&НаСервереБезКонтекста
Функция ПолучитьДанныеПоОСНаСервере(СсылкаНаОС)
Результат = Новый Структура("Дт, Кт", 0, 0);
Если Не ЗначениеЗаполнено(СсылкаНаОС) Тогда
Возврат Результат;
КонецЕсли;
МассивСчетов = Новый Массив;
МассивСчетов.Добавить(ПланыСчетов.Хозрасчетный.ОСвОрганизации);
МассивСчетов.Добавить(ПланыСчетов.Хозрасчетный.АмортизацияОС_01);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ХозрасчетныйОстатки.СуммаОстатокДт КАК СуммаДт,
| ХозрасчетныйОстатки.СуммаОстатокКт КАК СуммаКт
|ИЗ
| РегистрБухгалтерии.Хозрасчетный.Остатки(
| ,
| Счет В (&МассивСчетов),
| ,
| Субконто1 = &ОС) КАК ХозрасчетныйОстатки";
Запрос.УстановитьПараметр("ОС", СсылкаНаОС);
Запрос.УстановитьПараметр("МассивСчетов", МассивСчетов);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Если Выборка.СуммаДт <> 0 Тогда
Результат.Дт = Выборка.СуммаДт;
КонецЕсли;
Если Выборка.СуммаКт <> 0 Тогда
Результат.Кт = Выборка.СуммаКт;
КонецЕсли;
КонецЦикла;
Возврат Результат;
КонецФункции
Если справочник содержит большое количество элементов или с базой одновременно работает много пользователей, постоянные серверные вызовы при навигации создадут избыточную нагрузку на сервер. В таких случаях рекомендуем рассмотреть альтернативы: