如何确定MVVM应用程序中的不活动?

时间:2011-02-01 16:46:29

标签: wpf mvvm event-hooking

我有一个MVVM kiosk应用程序,当它在一段时间内处于非活动状态时我需要重新启动。我正在使用Prism和Unity来促进MVVM模式。我重新启动了,我甚至知道如何处理计时器。我想知道的是如何知道什么时候发生了活动,即任何鼠标事件。我知道如何做到这一点的唯一方法是订阅主窗口的预览鼠标事件。这打破了MVVM的想法,不是吗?

我已经考虑将我的窗口暴露为将这些事件暴露给我的应用程序的接口,但这需要窗口实现该接口,这似乎也打破了MVVM。

4 个答案:

答案 0 :(得分:4)

另一种选择是使用Windows API方法GetLastInputInfo

一些警告

  • 我假设Windows因为它是WPF
  • 检查您的自助服务终端是否支持GetLastInputInfo
  • 我对MVVM一无所知。此方法使用与UI无关的技术,因此我认为它适用于您。

用法很简单。调用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 :(得分:1)

您可以使用MVVM Light's EventToCommand行为将MouseMove / MouseLeftButtonDown事件链接到命令。这通常是在混合中完成的,因为它非常简单。

如果您没有混合,请参考以下示例xaml:

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

i: 是Blend.Interactivity的xml命名空间。

答案 2 :(得分:1)

这不是正式答案,但这是我对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 
}

答案 3 :(得分:0)

  

我知道如何做的唯一方法是订阅主窗口的预览鼠标事件。这打破了MVVM的想法,不是吗?

这实际上取决于你是如何做到的。

您可以非常轻松地编写一个行为或附加属性,并将其挂钩到此事件中,并使用它在ViewModel中触发ICommand。这样,您基本上将“事情发生”事件推送到VM,您可以在其中完全处理业务逻辑。