2011-02-10 13 views
28

Sto cercando di gestire l'inattività e l'attività dell'utente in un'applicazione WPF per sbiadire alcune cose dentro e fuori. Dopo molte ricerche, ho deciso di andare con la soluzione molto elegante (almeno secondo me) di Hans Passant con il here.Inattività e attività WPF

C'è solo uno svantaggio: finché il cursore rimane in cima alla finestra, l'evento PreProcessInput viene continuamente attivato. Sto avendo un'applicazione a schermo intero, quindi questo lo uccide. Qualsiasi idea su come ignorare questo comportamento sarebbe molto apprezzata.

public partial class MainWindow : Window 
{ 
    readonly DispatcherTimer activityTimer; 

    public MainWindow() 
    { 
     InitializeComponent(); 

     InputManager.Current.PreProcessInput += Activity; 

     activityTimer = new DispatcherTimer 
     { 
      Interval = TimeSpan.FromSeconds(10), 
      IsEnabled = true 
     }; 
     activityTimer.Tick += Inactivity; 
    } 

    void Inactivity(object sender, EventArgs e) 
    { 
     rectangle1.Visibility = Visibility.Hidden; // Update 
     // Console.WriteLine("INACTIVE " + DateTime.Now.Ticks); 
    } 

    void Activity(object sender, PreProcessInputEventArgs e) 
    { 
     rectangle1.Visibility = Visibility.Visible; // Update 
     // Console.WriteLine("ACTIVE " + DateTime.Now.Ticks); 

     activityTimer.Stop(); 
     activityTimer.Start(); 
    } 
} 

Aggiorna

ho potuto restringere il comportamento descritto meglio (vedi l'aggiornamento rectangle1.Visibility nel codice precedente). Finché il cursore si trova in cima alla finestra e ad esempio viene modificato Visibility di un controllo, viene sollevato il numero PreProcessInput. Forse sto fraintendendo lo scopo dell'evento PreProcessInput e quando si attiva. MSDN non è stato molto utile qui.

+0

Questo codice funziona perfettamente per me e 'PreProcessInput' non viene generato quando il mouse si trova ancora su' Window'. Hai lo stesso effetto se crei una piccola app con solo il codice che hai postato? Quale versione .NET stai usando? –

+0

@Meleak: Grazie!Effettivamente, funziona con il codice appena sopra (vergogna su di me). Ad ogni modo, nel mio progetto ho ancora uno strano comportamento. Sto ricercando e restringendo ulteriormente questo aspetto e fornirò informazioni più dettagliate. Per completezza, sto usando .NET 4. –

+0

@Meleak: Ho aggiornato la domanda in modo che il comportamento sia effettivamente comprensibile. –

risposta

15

Potrei capire cosa ha causato il comportamento descritto.

Ad esempio quando l'Visibility di un controllo viene modificata, l'evento PreProcessInput è sollevata PreProcessInputEventArgs.StagingItem.Input del tipo InputReportEventArgs.

Il comportamento può essere evitato mediante filtrazione del InputEventArgs per i tipi MouseEventArgs e KeyboardEventArgs in caso OnActivity e verificare se si preme alcun tasto e lo posizione del cursore è ancora lo stesso come l'applicazione diventa inattiva.

public partial class MainWindow : Window 
{ 
    private readonly DispatcherTimer _activityTimer; 
    private Point _inactiveMousePosition = new Point(0, 0); 

    public MainWindow() 
    { 
     InitializeComponent(); 

     InputManager.Current.PreProcessInput += OnActivity; 
     _activityTimer = new DispatcherTimer { Interval = TimeSpan.FromMinutes(5), IsEnabled = true }; 
     _activityTimer.Tick += OnInactivity; 
    } 

    void OnInactivity(object sender, EventArgs e) 
    { 
     // remember mouse position 
     _inactiveMousePosition = Mouse.GetPosition(MainGrid); 

     // set UI on inactivity 
     rectangle.Visibility = Visibility.Hidden; 
    } 

    void OnActivity(object sender, PreProcessInputEventArgs e) 
    { 
     InputEventArgs inputEventArgs = e.StagingItem.Input; 

     if (inputEventArgs is MouseEventArgs || inputEventArgs is KeyboardEventArgs) 
     { 
      if (e.StagingItem.Input is MouseEventArgs) 
      { 
       MouseEventArgs mouseEventArgs = (MouseEventArgs)e.StagingItem.Input; 

       // no button is pressed and the position is still the same as the application became inactive 
       if (mouseEventArgs.LeftButton == MouseButtonState.Released && 
        mouseEventArgs.RightButton == MouseButtonState.Released && 
        mouseEventArgs.MiddleButton == MouseButtonState.Released && 
        mouseEventArgs.XButton1 == MouseButtonState.Released && 
        mouseEventArgs.XButton2 == MouseButtonState.Released && 
        _inactiveMousePosition == mouseEventArgs.GetPosition(MainGrid)) 
        return; 
      } 

      // set UI on activity 
      rectangle.Visibility = Visibility.Visible; 

      _activityTimer.Stop(); 
      _activityTimer.Start(); 
     } 
    } 
} 
+1

+1, Buono a sapersi! –

+0

Se sto usando questo da un ViewModel e quindi non ho accesso a 'MainGrid' (o agli elementi del modulo), c'è un altro modo per rilevare il movimento del mouse? – colmde

+0

Per dare un seguito al mio commento sopra: ho rimosso tutto il 'if (e.StagingItem.Input è MouseMoveEventArgs)' dichiarazione, e sta lavorando bene per me, la rilevazione sia clic e mouse si muove, ignorando il mouse semplicemente si libra sopra il applicazione. – colmde

1

Invece di ascoltare PreProcessInput hai provato PreviewMouseMove?

+0

Credo che questa sia una migliore alternativa. L'unico problema è che non è direttamente accessibile da un viewmodel come 'PreProcessInput'. Invece devi legarlo ad esso sulla 'MainWindow' della tua app. –

36

Abbiamo avuto un'esigenza simile per il nostro software ... è anche un'applicazione WPF e, come funzionalità di sicurezza, un client può configurare un'ora in cui gli utenti verranno disconnessi se sono inattivi.

Di seguito è la classe che ho fatto per avvolgere il codice di rilevamento di inattività (che utilizza la funzionalità integrata di Windows).

Abbiamo semplicemente un tick del timer sempre 1 secondo per verificare se il tempo di inattività è maggiore della soglia specificata ... richiede 0 CPU.

In primo luogo, ecco come utilizzare il codice:

var idleTime = IdleTimeDetector.GetIdleTimeInfo(); 

if (idleTime.IdleTime.TotalMinutes >= 5) 
{ 
    // They are idle! 
} 

È possibile utilizzare questo e anche fare in modo che il vostro WPF pieno proiettato app è "focalizzata" per realizzare i vostri bisogni:

using System; 
using System.Runtime.InteropServices; 

namespace BlahBlah 
{ 
    public static class IdleTimeDetector 
    { 
     [DllImport("user32.dll")] 
     static extern bool GetLastInputInfo(ref LASTINPUTINFO plii); 

     public static IdleTimeInfo GetIdleTimeInfo() 
     { 
      int systemUptime = Environment.TickCount, 
       lastInputTicks = 0, 
       idleTicks = 0; 

      LASTINPUTINFO lastInputInfo = new LASTINPUTINFO(); 
      lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo); 
      lastInputInfo.dwTime = 0; 

      if (GetLastInputInfo(ref lastInputInfo)) 
      { 
       lastInputTicks = (int)lastInputInfo.dwTime; 

       idleTicks = systemUptime - lastInputTicks; 
      } 

      return new IdleTimeInfo 
      { 
       LastInputTime = DateTime.Now.AddMilliseconds(-1 * idleTicks), 
       IdleTime = new TimeSpan(0, 0, 0, 0, idleTicks), 
       SystemUptimeMilliseconds = systemUptime, 
      }; 
     } 
    } 

    public class IdleTimeInfo 
    { 
     public DateTime LastInputTime { get; internal set; } 

     public TimeSpan IdleTime { get; internal set; } 

     public int SystemUptimeMilliseconds { get; internal set; } 
    } 

    internal struct LASTINPUTINFO 
    { 
     public uint cbSize; 
     public uint dwTime; 
    } 
} 
+0

Questa piccola bellezza mi ha salvato una buona quantità di codice, grazie. L'utilizzo di funzioni integrate di Windows è un eccellente suggerimento. – Digitalsa1nt

0

I implementa la soluzione in una classe IdleDetector. Ho migliorato un po 'il codice. Il rilevatore Iddle lancia un IsIdle che può essere intercettato! Dà questo! Aspetto alcuni commenti

public class IdleDetector 
{ 
    private readonly DispatcherTimer _activityTimer; 
    private Point _inactiveMousePosition = new Point(0, 0); 

    private IInputElement _inputElement; 
    private int _idleTime = 300; 

    public event EventHandler IsIdle; 

    public IdleDetector(IInputElement inputElement, int idleTime) 
    { 
     _inputElement = inputElement; 
     InputManager.Current.PreProcessInput += OnActivity; 
     _activityTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(idleTime), IsEnabled = true }; 
     _activityTimer.Tick += OnInactivity; 
    } 

    public void ChangeIdleTime(int newIdleTime) 
    { 
     _idleTime = newIdleTime; 

     _activityTimer.Stop(); 
     _activityTimer.Interval = TimeSpan.FromSeconds(newIdleTime); 
     _activityTimer.Start(); 
    } 

    void OnInactivity(object sender, EventArgs e) 
    { 
     _inactiveMousePosition = Mouse.GetPosition(_inputElement); 
     _activityTimer.Stop(); 
     IsIdle?.Invoke(this, new EventArgs()); 
    } 

    void OnActivity(object sender, PreProcessInputEventArgs e) 
    { 
     InputEventArgs inputEventArgs = e.StagingItem.Input; 

     if (inputEventArgs is MouseEventArgs || inputEventArgs is KeyboardEventArgs) 
     { 
      if (e.StagingItem.Input is MouseEventArgs) 
      { 
       MouseEventArgs mouseEventArgs = (MouseEventArgs)e.StagingItem.Input; 

       // no button is pressed and the position is still the same as the application became inactive 
       if (mouseEventArgs.LeftButton == MouseButtonState.Released && 
        mouseEventArgs.RightButton == MouseButtonState.Released && 
        mouseEventArgs.MiddleButton == MouseButtonState.Released && 
        mouseEventArgs.XButton1 == MouseButtonState.Released && 
        mouseEventArgs.XButton2 == MouseButtonState.Released && 
        _inactiveMousePosition == mouseEventArgs.GetPosition(_inputElement)) 
        return; 
      } 

      _activityTimer.Stop(); 
      _activityTimer.Start(); 
     } 
    } 
} 
Problemi correlati