2011-02-01 48 views
5

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

我想过把我的窗口暴露为一个接口,将这些事件暴露给我的应用程序,但这需要窗口实现那个接口,这似乎也打破了MVVM。

回答

4

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

一些cavets

  • 我假设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。

增加了取消注册的功能。

在公共方法中添加了线程检查,因为它不再是线程安全的。

+0

@Tergiver,我要试试这个。 `System.Windows.Threading.DispatcherTimer`回发与创建它的分派器相同的线程。这样我可以避免使用锁。只需要添加该婴儿的WindowsBase参考。 – Jordan 2011-02-01 20:48:53

+0

@Jordan:我使用了System.Timers.Timer,因此它不依赖于WPF,但这是个好主意。 – Tergiver 2011-02-01 20:51:32

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> 

哪里我:是Blend.Interactivity一个XML命名空间。

0

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

这真的取决于你是如何做到的。

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

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 
} 
相关问题