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