2011-02-10 15 views
28

Estoy tratando de manejar la inactividad y la actividad del usuario en una aplicación WPF para atenuar algunas cosas dentro y fuera. Después de mucha investigación, decidí ir con (al menos en mi opinión) una solución muy elegante. Hans Passant publicó here.WPF inactividad y actividad

Solo hay un inconveniente: mientras el cursor permanezca en la parte superior de la ventana, el evento PreProcessInput se dispara continuamente. Tengo una aplicación de pantalla completa, así que esto lo mata. Cualquier idea sobre cómo puedo evitar este comportamiento sería muy apreciada.

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(); 
    } 
} 

actualización

pude reducir el comportamiento descrito mejor (véase la actualización rectangle1.Visibility en el código anterior). Siempre que el cursor descanse en la parte superior de la ventana y, por ejemplo, se cambie Visibility de un control, se levantará PreProcessInput. Tal vez estoy malinterpretando el propósito del evento PreProcessInput y cuando se dispara. MSDN no fue muy útil aquí.

+0

Ese código funciona muy bien para mí y 'PreProcessInput' no aparece cuando el mouse aún está sobre la' Ventana'. ¿Obtiene el mismo efecto si crea una aplicación pequeña solo con el código que publicó? ¿Qué versión de .NET estás usando? –

+0

@Meleak: ¡Gracias!De hecho, funciona solo con el código anterior (lástima de mí). De todos modos, en mi proyecto todavía tengo ese comportamiento extraño. Estoy investigando y reduciendo esto más y proporcionaré información más detallada. Para completar, estoy usando .NET 4. –

+0

@Meleak: He actualizado la pregunta para que el comportamiento sea realmente comprensible. –

Respuesta

15

Pude averiguar qué causó el comportamiento descrito.

Por ejemplo, cuando se cambia el Visibility de un control, el evento PreProcessInput se eleva con PreProcessInputEventArgs.StagingItem.Input del tipo InputReportEventArgs.

El comportamiento se puede evitar mediante el filtrado de la InputEventArgs para los tipos MouseEventArgs y KeyboardEventArgs en caso OnActivity y para verificar si no se pulsa el botón del ratón y la posición del cursor sigue siendo la misma que la solicitud se hizo inactivo.

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, ¡es bueno saberlo! –

+0

Si estoy usando esto desde un ViewModel y por lo tanto no tengo acceso a 'MainGrid' (o elementos de formulario), ¿hay alguna otra forma de detectar el movimiento del mouse? – colmde

+0

Como seguimiento de mi comentario anterior: eliminé todo el enunciado 'if (e.StagingItem.Input es MouseMoveEventArgs)', y está funcionando bien para mí, detectando tanto clics como movimientos del mouse, sin tener en cuenta que el mouse simplemente se cierne sobre el solicitud. – colmde

1

En vez de escuchar PreProcessInput ¿Has probado PreviewMouseMove?

+0

Creo que esta es una mejor alternativa. El único problema con esto es que no se puede acceder directamente desde un modelo de vista como 'PreProcessInput'. En su lugar, debes vincularlo en la 'ventana principal' de tu aplicación. –

36

Hemos tenido una necesidad similar para nuestro software ... también es una aplicación WPF, y como una característica de seguridad, un cliente puede configurar una hora en la que sus usuarios serán desconectados si están inactivos.

A continuación se muestra la clase que hice para ajustar el código de detección de inactividad (que utiliza la funcionalidad incorporada de Windows).

Simplemente tenemos un tic ticker siempre 1 segundo para comprobar si el tiempo de inactividad es mayor que el umbral especificado ... toma 0 CPU.

En primer lugar, aquí es cómo utilizar el código:

var idleTime = IdleTimeDetector.GetIdleTimeInfo(); 

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

Puede usar esto y también asegurarse de que su WPF aplicación completa tamizada se "centra" para alcanzar sus necesidades:

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

Esta pequeña belleza me ha ahorrado una cantidad decente de codificación, gracias. Usar ventanas incorporadas es una excelente sugerencia. – Digitalsa1nt

0

Implemento la solución en una clase IdleDetector. He mejorado un poco el código. ¡El detector Iddle lanza un IsIdle que puede ser interceptado! ¡Da eso! Espero algunos comentarios.

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(); 
     } 
    } 
} 
Cuestiones relacionadas