Как определить неактивность в приложении MVVM? - PullRequest
5 голосов
/ 01 февраля 2011

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

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

Ответы [ 4 ]

4 голосов
/ 01 февраля 2011

Другим вариантом является использование метода 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.

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

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

1 голос
/ 02 февраля 2011

Это не официальный ответ, но вот моя версия 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 
}
1 голос
/ 01 февраля 2011

Возможно, вы можете использовать MVVM Light для поведения EventToCommand, чтобы связать событие 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.

0 голосов
/ 01 февраля 2011

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

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

Вы можете довольно легко написать поведение или присоединенное свойство, которое вы подключаете к этому.событие и использовать его для запуска ICommand в вашей ViewModel.Таким образом, вы в основном отправляете событие «Что-то случилось» на виртуальную машину, где вы можете полностью обработать это в своей бизнес-логике.

...