我有一个MVVM自助服务终端应用程序,当它在一定时间内处于非活动状态时,我需要重新启动它。我使用Prism和Unity来促进MVVM模式。我重新启动了,我甚至知道如何处理定时器。我想知道的是如何知道何时发生了活动,即任何鼠标事件。我知道如何做到这一点的唯一方法是订阅主窗口的预览鼠标事件。这打破了MVVM的想法,不是吗?如何确定MVVM应用程序中的不活动?
我想过把我的窗口暴露为一个接口,将这些事件暴露给我的应用程序,但这需要窗口实现那个接口,这似乎也打破了MVVM。
我有一个MVVM自助服务终端应用程序,当它在一定时间内处于非活动状态时,我需要重新启动它。我使用Prism和Unity来促进MVVM模式。我重新启动了,我甚至知道如何处理定时器。我想知道的是如何知道何时发生了活动,即任何鼠标事件。我知道如何做到这一点的唯一方法是订阅主窗口的预览鼠标事件。这打破了MVVM的想法,不是吗?如何确定MVVM应用程序中的不活动?
我想过把我的窗口暴露为一个接口,将这些事件暴露给我的应用程序,但这需要窗口实现那个接口,这似乎也打破了MVVM。
另一种选择是使用Windows API方法GetLastInputInfo。
一些cavets
用法很简单。调用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。
增加了取消注册的功能。
在公共方法中添加了线程检查,因为它不再是线程安全的。
您可以使用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命名空间。
我知道如何做到这一点的唯一方法是订阅主窗口的预览鼠标事件。这打破了MVVM的想法,不是吗?
这真的取决于你是如何做到的。
您可以非常轻松地编写一个行为或附加属性,您可以挂钩到此事件中并使用它触发ViewModel中的ICommand。这样,你基本上将一个“发生了什么事情”的事件推到VM上,你可以在业务逻辑中完全处理这个事件。
这不是一个官方的回答,但这里是我的版本的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
}
@Tergiver,我要试试这个。 `System.Windows.Threading.DispatcherTimer`回发与创建它的分派器相同的线程。这样我可以避免使用锁。只需要添加该婴儿的WindowsBase参考。 – Jordan 2011-02-01 20:48:53
@Jordan:我使用了System.Timers.Timer,因此它不依赖于WPF,但这是个好主意。 – Tergiver 2011-02-01 20:51:32