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