Как определить неактивность в приложении MVVM?

У меня есть приложение киоска MVVM, которое мне нужно перезапустить, когда оно неактивно в течение определенного периода времени. Я использую Prism и Unity для упрощения шаблона MVVM. У меня перезапуск, и я даже знаю, как справиться с таймером. То, что я хочу знать, это как узнать, когда произошла активность, то есть любое событие мыши. Единственный способ, которым я знаю, как это сделать, это подписаться на события мыши предварительного просмотра главного окна. Это нарушает мысль MVVM, не так ли?

Я думал о том, чтобы выставить свое окно как интерфейс, который отображает эти события для моего приложения, но для этого потребуется, чтобы окно реализовало тот интерфейс, который также, кажется, нарушает MVVM.

4 ответа

Решение

Другой вариант - использовать метод Windows API GetLastInputInfo.

Некоторые пещеры

  • Я предполагаю, что Windows, потому что это WPF
  • Проверьте, поддерживает ли ваш киоск GetLastInputInfo
  • Я ничего не знаю о MVVM. Этот метод использует технику, которая не зависит от пользовательского интерфейса, поэтому я думаю, что она будет работать для вас.

Использование простое. Вызовите UserIdleMonitor.RegisterForNotification. Вы передаете метод уведомления и TimeSpan. Если пользовательская активность происходит, а затем прекращается в течение указанного периода, вызывается метод уведомления. Вы должны повторно зарегистрироваться, чтобы получить другое уведомление, и можете отменить регистрацию в любое время. Если в течение 49,7 дней нет активности (плюс idlePeriod), будет вызван метод уведомления.

public static class UserIdleMonitor
{
    static UserIdleMonitor()
    {
        registrations = new List<Registration>();
        timer = new DispatcherTimer(TimeSpan.FromSeconds(1.0), DispatcherPriority.Normal, TimerCallback, Dispatcher.CurrentDispatcher);
    }

    public static TimeSpan IdleCheckInterval
    {
        get { return timer.Interval; }
        set
        {
            if (Dispatcher.CurrentDispatcher != timer.Dispatcher)
                throw new InvalidOperationException("UserIdleMonitor can only be used from one thread.");
            timer.Interval = value;
        }
    }

    public sealed class Registration
    {
        public Action NotifyMethod { get; private set; }
        public TimeSpan IdlePeriod { get; private set; }
        internal uint RegisteredTime { get; private set; }

        internal Registration(Action notifyMethod, TimeSpan idlePeriod)
        {
            NotifyMethod = notifyMethod;
            IdlePeriod = idlePeriod;
            RegisteredTime = (uint)Environment.TickCount;
        }
    }

    public static Registration RegisterForNotification(Action notifyMethod, TimeSpan idlePeriod)
    {
        if (notifyMethod == null)
            throw new ArgumentNullException("notifyMethod");
        if (Dispatcher.CurrentDispatcher != timer.Dispatcher)
            throw new InvalidOperationException("UserIdleMonitor can only be used from one thread.");

        Registration registration = new Registration(notifyMethod, idlePeriod);

        registrations.Add(registration);
        if (registrations.Count == 1)
            timer.Start();

        return registration;
    }

    public static void Unregister(Registration registration)
    {
        if (registration == null)
            throw new ArgumentNullException("registration");
        if (Dispatcher.CurrentDispatcher != timer.Dispatcher)
            throw new InvalidOperationException("UserIdleMonitor can only be used from one thread.");

        int index = registrations.IndexOf(registration);
        if (index >= 0)
        {
            registrations.RemoveAt(index);
            if (registrations.Count == 0)
                timer.Stop();
        }
    }

    private static void TimerCallback(object sender, EventArgs e)
    {
        LASTINPUTINFO lii = new LASTINPUTINFO();
        lii.cbSize = Marshal.SizeOf(typeof(LASTINPUTINFO));
        if (GetLastInputInfo(out lii))
        {
            TimeSpan idleFor = TimeSpan.FromMilliseconds((long)unchecked((uint)Environment.TickCount - lii.dwTime));
            //Trace.WriteLine(String.Format("Idle for {0}", idleFor));

            for (int n = 0; n < registrations.Count; )
            {
                Registration registration = registrations[n];

                TimeSpan registeredFor = TimeSpan.FromMilliseconds((long)unchecked((uint)Environment.TickCount - registration.RegisteredTime));
                if (registeredFor >= idleFor && idleFor >= registration.IdlePeriod)
                {
                    registrations.RemoveAt(n);
                    registration.NotifyMethod();
                }
                else n++;
            }

            if (registrations.Count == 0)
                timer.Stop();
        }
    }

    private static List<Registration> registrations;
    private static DispatcherTimer timer;

    private struct LASTINPUTINFO
    {
        public int cbSize;
        public uint dwTime;
    }

    [DllImport("User32.dll")]
    private extern static bool GetLastInputInfo(out LASTINPUTINFO plii);
}

обновленный

Исправлена ​​ошибка, из-за которой при попытке перерегистрации с помощью метода уведомления вы могли зайти в тупик.

Исправлена ​​неподписанная математика и добавлена ​​непроверенная.

Небольшая оптимизация в обработчике таймера для размещения уведомлений только по мере необходимости.

Закомментировал вывод отладочной информации.

Изменено использование DispatchTimer.

Добавлена ​​возможность отменить регистрацию.

Добавлены проверки потоков в общедоступных методах, так как это больше не потокобезопасно.

Вы можете использовать поведение EventToCommand MVVM Light, чтобы связать событие MouseMove/MouseLeftButtonDown с командой. Обычно это делается в смеси, потому что это действительно легко.

Вот несколько примеров xaml, если у вас нет blend:

<Grid>
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="MouseLeftButtonDown">
      <GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding theCommand} />
    </i:EventTrigger>
  </i:Interaction.Triggers>
</Grid>

Где i: пространство имен xml для Blend.Interactivity.

Это не официальный ответ, но вот моя версия UserIdleMonitor для тех, кто заинтересован:

public class UserIdleMonitor
{
    private DispatcherTimer _timer;
    private TimeSpan _timeout;
    private DateTime _startTime;

    public event EventHandler Timeout;

    public UserIdleMonitor(TimeSpan a_timeout)
    {
        _timeout = a_timeout;

        _timer = new DispatcherTimer(DispatcherPriority.Normal, Dispatcher.CurrentDispatcher);
        _timer.Interval = TimeSpan.FromMilliseconds(100);
        _timer.Tick += new EventHandler(timer_Tick);
    }

    public void Start()
    {
        _startTime = new DateTime();
        _timer.Start();
    }

    public void Stop()
    {
        _timer.Stop();
    }

    private void timer_Tick(object sender, EventArgs e)
    {
        LASTINPUTINFO lii = new LASTINPUTINFO();
        lii.cbSize = Marshal.SizeOf(typeof(LASTINPUTINFO));
        if (GetLastInputInfo(out lii))
        {
            TimeSpan idleFor = TimeSpan.FromMilliseconds((long)unchecked((uint)Environment.TickCount - lii.dwTime));

            TimeSpan aliveFor = TimeSpan.FromMilliseconds((long)unchecked((uint)Environment.TickCount - _startTime.Millisecond));
            Debug.WriteLine(String.Format("aliveFor = {0}, idleFor = {1}, _timeout = {2}", aliveFor, idleFor, _timeout));
            if (aliveFor >= idleFor && idleFor >= _timeout)
            {
                _timer.Stop();
                if (Timeout != null)
                    Timeout.Invoke(this, EventArgs.Empty);
            }
        }
    }

    #region Win32 Stuff

    private struct LASTINPUTINFO
    {
        public int cbSize;
        public uint dwTime;
    }

    [DllImport("User32.dll")]
    private extern static bool GetLastInputInfo(out LASTINPUTINFO plii);

    #endregion 
}

Единственный способ, которым я знаю, как это сделать, это подписаться на события мыши предварительного просмотра главного окна. Это нарушает мысль MVVM, не так ли?

Это действительно зависит от того, как вы это делаете.

Вы можете довольно легко написать Behavior или Attached Property, которые вы подключаете к этому событию, и использовать их для запуска ICommand в вашей ViewModel. Таким образом, вы в основном отправляете событие "Нечто произошло" на виртуальную машину, где вы можете полностью обработать это в своей бизнес-логике.

Другие вопросы по тегам