Un'altra opzione è utilizzare il metodo API di Windows GetLastInputInfo.
Alcuni cavets
- sto supponendo di Windows perché è WPF
- Verificare se il chiosco supporta GetLastInputInfo
- Io non so niente di MVVM. Questo metodo utilizza una tecnica che è indipendente dall'interfaccia utente, quindi penserei che potrebbe funzionare per voi.
L'utilizzo è semplice. Chiama UserIdleMonitor.RegisterForNotification. Si passa in un metodo di notifica e un TimeSpan. Se l'attività dell'utente si verifica e quindi cessa per il periodo specificato, viene chiamato il metodo di notifica. Devi registrarti di nuovo per ricevere un'altra notifica e puoi annullare la registrazione in qualsiasi momento. Se non ci sono attività per 49,7 giorni (più idlePeriod), verrà chiamato il metodo di notifica.
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);
}
Aggiornato
Risolto il problema per cui se si è tentato di registrare nuovamente dal metodo di notifica si potrebbe deadlock.
Calcolo matematico senza segno e aggiunta deselezionato.
Leggera ottimizzazione nel gestore del timer per assegnare le notifiche solo se necessario.
Ha commentato l'output di debug.
modificato per utilizzare DispatchTimer.
Aggiunta la possibilità di annullare la registrazione.
Aggiunto controllo del thread nei metodi pubblici in quanto non è più thread-safe.
@Tergiver, ho intenzione di provare questo. 'System.Windows.Threading.DispatcherTimer' torna sullo stesso thread del Dispatcher per il quale è stato creato. In questo modo posso evitare di usare i lucchetti. Basta aggiungere il riferimento WindowsBase per quel bambino. – Jordan
@Jordan: Ho usato System.Timers.Timer in modo che non dipendesse dal WPF, ma questa è una buona idea. – Tergiver
@Tergiver, 'System.Windows.Threading.DispatcherTimer' è abbastanza fondamentale nel framework che è possibile utilizzarlo in WinForms o in un'altra interfaccia utente. – Jordan