При разработке управляемых форм в 1С:Предприятии часто возникает задача отслеживания активности пользователя. Например, нам может потребоваться автоматически завершать работу или сбрасывать данные формы, если пользователь длительное время не совершает никаких действий. Стандартного события "При изменении" для всей формы целиком, которое бы срабатывало при любом вводе данных или изменении элементов, не существует. Это создает определенные сложности, особенно когда на форме много элементов и ручное подключение обработчиков становится трудоемким. В этой статье мы подробно рассмотрим различные подходы и методы, которые помогут нам решить эту задачу.
Один из самых часто предлагаемых и достаточно эффективных подходов — это использование свойства формы Модифицированность совместно с обработчиком ожидания. Давайте разберем его подробнее.
Модифицированность: Это свойство формы принимает значение Истина, как только любые данные на форме были изменены пользователем, и Ложь, если форма не изменена или изменения были записаны. Важно понимать, что Модифицированность меняется только один раз при первом изменении. Если пользователь внес изменение, а затем перестал что-либо делать, свойство останется Истина, но не будет сигнализировать о дальнейшем бездействии.ОбработчикОжидания): Это механизм, который позволяет нам периодически выполнять определенный код на клиенте. Мы можем настроить его так, чтобы он срабатывал с заданным интервалом (например, раз в несколько секунд).Чтобы отслеживать длительное бездействие с учетом уже измененных данных, мы можем использовать следующую логику:
ОбработчикаОжидания: В обработчике ожидания мы периодически проверяем состояние.Модифицированность:
Модифицированность = Ложь) и пользователь не совершает действий, это означает, что он просто бездействует на пустой или неизмененной форме. В этом случае мы просто сбрасываем счетчик активности при каждом срабатывании обработчика ожидания или ничего не делаем, если нам нужно отслеживать бездействие только после начала ввода данных.Модифицированность = Истина), это значит, что пользователь что-то изменил. В этом случае нам нужно проверить, продолжает ли он быть активным. Мы можем запустить внутренний таймер или счетчик.Для более точного отслеживания активности, помимо Модифицированность, мы можем использовать дополнительную переменную, например, ПоследнееВремяАктивности. При каждом предполагаемом действии пользователя (вводе в поле, клике, изменении фокуса) мы обновляем это время. В обработчике ожидания мы сравниваем текущее время с ПоследнееВремяАктивности. Если разница превышает заданный лимит, то пользователя можно считать неактивным.
Рассмотрим пример реализации:
// В модуле формы
// Переменные формы
Перем мПоследнееВремяАктивности Экспорт; // Хранит время последнего действия пользователя
Перем мСчетчикБездействия Экспорт; // Счетчик для проверки бездействия
Перем мИнтервалБездействия Экспорт; // Максимально допустимое время бездействия в секундах
Перем мИнтервалОбработчикаОжидания Экспорт; // Интервал для ОбработчикаОжидания в сотых долях секунды
&НаКлиенте
Процедура ПриСозданииНаСервереПосле (Отказ, СтандартнаяОбработка)
// Устанавливаем параметры
мИнтервалБездействия = 300; // 5 минут (300 секунд)
мИнтервалОбработчикаОжидания = 5 * 100; // 5 секунд
мПоследнееВремяАктивности = ТекущаяДата();
мСчетчикБездействия = 0;
// Подключаем обработчик ожидания
ПодключитьОбработчикОжидания("ПроверитьАктивностьПользователя", мИнтервалОбработчикаОжидания);
КонецПроцедуры
&НаКлиенте
Процедура ПередЗакрытием (Отказ, СтандартнаяОбработка)
// Отключаем обработчик ожидания при закрытии формы
ОтключитьОбработчикОжидания("ПроверитьАктивностьПользователя");
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьАктивностьПользователя ()
// Устанавливаем текущее время как признак активности, чтобы обработчик ожидания сам себя не "усыпил"
// Если пользователь был активен (т.е. произошло какое-то другое событие на форме)
// то мПоследнееВремяАктивности будет обновлено в соответствующем обработчике.
// Если других событий не было, то нам нужно определить бездействие.
Если ТекущаяДата() - мПоследнееВремяАктивности > мИнтервалБездействия Тогда
Сообщить("Пользователь бездействует более " + Строка(мИнтервалБездействия) + " секунд. Завершаем работу формы.");
// Здесь можно добавить логику завершения работы формы или сеанса
Закрыть();
КонецЕсли;
// ВНИМАНИЕ: Если нужно отслеживать активность при каждом чихе,
// то мПоследнееВремяАктивности нужно обновлять во всех обработчиках ПриИзменении, ПриАктивизации,
// ПриНажатии и т.д. Если же нужен только факт изменения данных, то достаточно Модифицированность.
// Если мы хотим отслеживать именно отсутствие *любых* действий,
// то мПоследнееВремяАктивности должно обновляться во всех возможных обработчиках интерактивных действий.
// А если пользователь просто перемещается по полям без изменения данных,
// то можно использовать свойство формы ТекущийЭлемент, сравнивая его с предыдущим значением.
// Однако, для простоты и эффективности, часто используются методы из следующих пунктов.
КонецПроцедуры
// Пример обработчика для обновления времени активности
// Его нужно добавлять к каждому интерактивному элементу, который вы хотите считать "активностью"
&НаКлиенте
Процедура ОбновитьАктивность()
мПоследнееВремяАктивности = ТекущаяДата();
КонецПроцедуры
// Пример для поля ввода
&НаКлиенте
Процедура ПолеВводаПриИзменении(Элемент)
ОбновитьАктивность();
// Дальнейшая логика поля
КонецПроцедуры
// Пример для кнопки
&НаКлиенте
Процедура КнопкаНажатие(Элемент)
ОбновитьАктивность();
// Дальнейшая логика кнопки
КонецПроцедуры
// Пример для табличной части (при изменении строки или выборе)
&НаКлиенте
Процедура ТабличнаяЧастьПриАктивизацииСтроки(Элемент)
ОбновитьАктивность();
КонецПроцедуры
// Пример для табличной части (при изменении данных в ячейке)
&НаКлиенте
Процедура ТабличнаяЧастьПриИзменении(Элемент)
ОбновитьАктивность();
КонецПроцедуры
Этот подход требует тщательной проработки, так как нам нужно определить, какие именно действия пользователя мы считаем "активностью" и соответственно обновлять мПоследнееВремяАктивности. Для фиксации действий пользователя без привязки к каждому элементу управления можно использовать компоненту для перехвата ввода с клавиатуры и мыши.
Если наша основная цель — отловить именно изменение данных на форме, а не любое взаимодействие, то более надежным способом будет периодическое сравнение текущих значений реквизитов формы с их предыдущим "снимком".
Преимущества этого метода:
Недостатки:
Пример реализации:
// В модуле формы
// Переменные формы
Перем мПоследнийСнимокДанных Экспорт; // Хранит сериализованные данные формы
Перем мПоследнееВремяАктивностиДляДанных Экспорт; // Время последнего изменения данных
Перем мИнтервалБездействияДляДанных Экспорт; // Максимально допустимое время бездействия для данных
&НаКлиенте
Процедура ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)
мИнтервалБездействияДляДанных = 300; // 5 минут
мПоследнееВремяАктивностиДляДанных = ТекущаяДата();
// Создаем начальный снимок данных
мПоследнийСнимокДанных = ПолучитьСнимокДанныхФормы();
ПодключитьОбработчикОжидания("ПроверитьИзменениеДанныхФормы", 5 * 100); // Проверка каждые 5 секунд
КонецПроцедуры
&НаКлиенте
Процедура ПередЗакрытием(Отказ, СтандартнаяОбработка)
ОтключитьОбработчикОжидания("ПроверитьИзменениеДанныхФормы");
КонецПроцедуры
&НаКлиенте
Функция ПолучитьСнимокДанныхФормы()
// Этот метод должен собирать все значимые реквизиты формы.
// Для простых реквизитов:
// СтруктураДанных = Новый Структура;
// СтруктураДанных.Вставить("Реквизит1", Реквизит1);
// СтруктураДанных.Вставить("Реквизит2", Реквизит2);
// Для табличных частей:
// МассивСтрокТЧ = Новый Массив;
// Для Каждого СтрокаТЧ Из Объект.ТабличнаяЧасть Цикл
// МассивСтрокТЧ.Добавить(Новый Структура("Поле1,Поле2", СтрокаТЧ.Поле1, СтрокаТЧ.Поле2));
// КонецЦикла;
// СтруктураДанных.Вставить("ТабличнаяЧасть", МассивСтрокТЧ);
// Проще всего сериализовать всю форму или ее часть.
// Внимание: Сериализация объекта формы может быть медленной для больших форм.
// Поэтому рекомендуется сериализовывать только те реквизиты, которые могут меняться.
// Если вам нужно отслеживать только те реквизиты, которые являются полями ввода,
// можно пройтись по элементам формы и собрать их значения.
// Пример сбора реквизитов в структуру для последующей сериализации:
ПеременныеДляСнимка = Новый Соответствие();
Для Каждого РеквизитФормы Из Метаданные.РеквизитыФормы Цикл
// Проверяем, что реквизит доступен для чтения и не является служебным (например, Элементы, Объект)
Если НЕ СтроковыеФункцииКлиентСервер.Найти(РеквизитФормы.Имя, "Элементы") И
НЕ СтроковыеФункцииКлиентСервер.Найти(РеквизитФормы.Имя, "Объект") Тогда
Попытка
ПеременныеДляСнимка.Вставить(РеквизитФормы.Имя, ЭтотОбъект[РеквизитФормы.Имя]);
Исключение
// Некоторые реквизиты могут быть недоступны на клиенте
КонецПопытки;
КонецЕсли;
КонецЦикла;
// Для объекта формы
Если ТипЗнч(Объект) = Тип("ДокументОбъект") ИЛИ ТипЗнч(Объект) = Тип("СправочникОбъект") Тогда
ПеременныеДляСнимка.Вставить("Объект", Объект); // Для сравнения всего объекта
КонецЕсли;
Возврат СериализаторXDTO.ЗаписатьXDTO(ПеременныеДляСнимка);
КонецФункции
&НаКлиенте
Процедура ПроверитьИзменениеДанныхФормы()
НовыйСнимокДанных = ПолучитьСнимокДанныхФормы();
Если НовыйСнимокДанных <> мПоследнийСнимокДанных Тогда
// Данные на форме изменились
мПоследнееВремяАктивностиДляДанных = ТекущаяДата();
мПоследнийСнимокДанных = НовыйСнимокДанных;
Иначе
// Данные не менялись, проверяем время бездействия
Если ТекущаяДата() - мПоследнееВремяАктивностиДляДанных > мИнтервалБездействияДляДанных Тогда
Сообщить("Данные на форме не менялись более " + Строка(мИнтервалБездействияДляДанных) + " секунд. Завершаем работу формы.");
// Здесь ваша логика завершения работы
Закрыть();
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Обратите внимание, что функция ПолучитьСнимокДанныхФормы() является ключевой и должна быть адаптирована под специфику вашей формы, чтобы эффективно собирать все значимые реквизиты для сравнения. Сериализация всего объекта формы может быть неоптимальной, лучше сериализовать конкретные реквизиты или структуру данных, собранную вручную.
Если на форме много полей и ручное добавление обработчиков к каждому элементу кажется невозможным, мы можем автоматизировать этот процесс при создании формы.
ПриСозданииНаСервере или ПриСозданииНаСервереПосле) мы можем в цикле обойти все элементы управления формы.ПриИзменении (например, поле ввода, флажок, поле выбора), мы можем программно установить обработчик с помощью метода УстановитьДействие. Все элементы будут вызывать одну и ту же общую процедуру.Пример реализации:
// В модуле формы
// Переменные формы
Перем мПоследнееВремяАктивностиЭкземпляраФормы Экспорт;
&НаКлиенте
Процедура ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)
мПоследнееВремяАктивностиЭкземпляраФормы = ТекущаяДата();
// Обходим элементы формы и подключаем обработчики
Для Каждого ЭлементФормы Из Элементы Цикл
Если ЭлементФормы.Тип = Тип("ПолеВвода") Или
ЭлементФормы.Тип = Тип("Флажок") Или
ЭлементФормы.Тип = Тип("ПолеВыбора") Или
ЭлементФормы.Тип = Тип("ПолеПереключателя") Тогда
// Проверяем, есть ли у элемента событие "ПриИзменении"
// и не имеет ли он уже свой собственный обработчик, если мы хотим его сохранить
Попытка
ЭлементФормы.УстановитьДействие("ПриИзменении", "ОбновитьВремяАктивности");
Исключение
// Игнорируем ошибки, если у элемента нет такого события или оно уже занято
КонецПопытки;
ИначеЕсли ЭлементФормы.Тип = Тип("ТаблицаФормы") Тогда
// Для табличных частей можно подключить ПриИзменении, ПриАктивизацииСтроки и т.д.
Попытка
ЭлементФормы.УстановитьДействие("ПриИзменении", "ОбновитьВремяАктивности");
Исключение КонецПопытки;
Попытка
ЭлементФормы.УстановитьДействие("ПриАктивизацииСтроки", "ОбновитьВремяАктивности");
Исключение КонецПопытки;
// И так далее для других событий табличной части
КонецЕсли;
КонецЦикла;
// Также можно подключить обработчик ожидания для проверки бездействия
ПодключитьОбработчикОжидания("ПроверитьАктивностьПользователяПоСобытиям", 5 * 100); // Каждые 5 секунд
КонецПроцедуры
&НаКлиенте
Процедура ПередЗакрытием(Отказ, СтандартнаяОбработка)
ОтключитьОбработчикОжидания("ПроверитьАктивностьПользователяПоСобытиям");
КонецПроцедуры
&НаКлиенте
Процедура ОбновитьВремяАктивности(Элемент = Неопределено)
// Эта процедура будет вызываться всеми элементами,
// к которым мы программно подключили обработчик.
мПоследнееВремяАктивностиЭкземпляраФормы = ТекущаяДата();
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьАктивностьПользователяПоСобытиям()
Если ТекущаяДата() - мПоследнееВремяАктивностиЭкземпляраФормы > 300 Тогда // 5 минут бездействия
Сообщить("Пользователь не совершал действий более 5 минут. Завершаем работу формы.");
Закрыть();
КонецЕсли;
КонецПроцедуры
Важный аспект: Если у элемента уже есть назначенный обработчик события ПриИзменении, то УстановитьДействие перезапишет его. Если нам нужно сохранить существующую логику, придется либо объединять обработчики, либо вызывать старый обработчик из нового, что усложняет код. Лучше использовать этот метод для форм, где элементы изначально не имеют своей специфичной логики ПриИзменении, или где эта логика может быть универсальной и централизованной.
Для более крупных систем с множеством форм, где необходимо отслеживать активность пользователя в целом по сеансу, а не только в рамках одной формы, имеет смысл создать централизованный механизм.
ПриСозданииНаСервереПосле всех форм вызывать общую процедуру, которая регистрирует форму и подключает к ней ОбработчикОжидания, вызывающий централизованную функцию.ОбработчикОжидания, который подключается при старте клиентского приложения и работает на уровне всего сеанса. Он будет периодически проверять время последней активности.Такой подход обеспечивает масштабируемость и уменьшает дублирование кода, но требует более сложной начальной настройки.
Если большая часть пользователей работает через веб-клиент, то наиболее "правильным" и надежным решением является использование серверных тайм-аутов сеанса. Этот механизм реализуется не средствами 1С-кода формы, а на уровне инфраструктуры.
Этот метод является наиболее эффективным для веб-клиентов, так как он не требует дополнительной логики на стороне 1С-приложения, разгружает клиентскую часть и централизованно управляется администратором системы.
Мы рассмотрели несколько подходов к решению задачи отслеживания активности пользователя на форме в 1С:Предприятии. Выбор конкретного метода зависит от ваших требований:
УстановитьДействие будет эффективным.Помните, что любое периодическое выполнение кода (ОбработчикОжидания) потребляет ресурсы клиента, поэтому выбирайте оптимальный интервал для проверки, чтобы не создавать излишнюю нагрузку на систему.