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