2011-02-10 16 views
28

Próbuję radzić sobie z brakiem aktywności użytkownika i działaniem w aplikacji WPF, aby zanikać niektóre rzeczy. Po wielu badaniach zdecydowałem się pójść z (przynajmniej moim zdaniem) bardzo eleganckim rozwiązaniem Hans Passant opublikowanym here.Bezczynność i aktywność WPF

Jest tylko jeden minus: tak długo, jak kursor pozostaje na górze okna, wydarzenie PreProcessInput jest stale wyzwalane. Mam aplikację pełnoekranową, więc to zabija. Wszelkie pomysły na obejście tego zachowania będą najbardziej doceniane.

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

Aktualizacja

mogę zawęzić opisanego zachowania lepsze (patrz aktualizację w powyższym kodzie rectangle1.Visibility). Dopóki kursor spoczywa na górnej części okna i na przykład zmieniono kontrolkę Visibility, podnosi się PreProcessInput. Być może nie rozumiem celu zdarzenia PreProcessInput i kiedy jest on uruchamiany. MSDN nie był tu zbyt pomocny.

+0

Ten kod działa doskonale dla mnie i 'PreProcessInput' nie jest wywoływany, gdy mysz nadal znajduje się nad' Oknem'. Czy masz taki sam efekt, jeśli utworzysz małą aplikację z samym kodem, który opublikowałeś? Którą wersję .NET używasz? –

+0

@Meleak: Dzięki!Rzeczywiście działa tylko z powyższym kodem (wstyd mi). W każdym razie w moim projekcie wciąż mam dziwne zachowanie. Zbieram je i zawężam bardziej i dostarczę bardziej szczegółowych informacji. Dla kompletności używam .NET 4. –

+0

@Meleak: Zaktualizowałem pytanie tak, aby zachowanie było rzeczywiście zrozumiałe. –

Odpowiedz

15

Mogłem dowiedzieć się, co spowodowało opisane zachowanie.

Na przykład gdy Visibility z kontroli zostanie zmienione, wydarzenie PreProcessInput jest podniesiona z PreProcessInputEventArgs.StagingItem.Input typu InputReportEventArgs.

Zachowanie można uniknąć poprzez filtrowanie InputEventArgs dla typów MouseEventArgs i KeyboardEventArgs w przypadku OnActivity i sprawdzić, czy nie jest wciśnięty przycisk myszy i pozycja kursora jest wciąż taka sama, jak aplikacja stała się nieaktywna.

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, dobrze wiedzieć! –

+0

Jeśli używam tego z wewnątrz ViewModel i dlatego nie mam dostępu do 'MainGrid' (lub elementów formularza), czy istnieje inny sposób wykrywania ruchu myszy? – colmde

+0

Jako kontynuację powyższego komentarza: usunąłem całą instrukcję 'if (e.StagingItem.Input is MouseMoveEventArgs)' i działa to dobrze dla mnie, wykrywając zarówno kliknięcia, jak i ruchy myszą, ignorując mysz po prostu unoszącą się nad podanie. – colmde

1

Zamiast słuchać PreProcessInput wypróbowałeś PreviewMouseMove?

+0

Wierzę, że to jest lepsze alternatywny. Jedynym problemem jest to, że nie jest dostępny bezpośrednio z modelu podglądu typu 'PreProcessInput'. Zamiast tego musisz powiązać go z 'MainWindow' twojej aplikacji. –

36

Mamy podobne zapotrzebowanie na nasze oprogramowanie ... jest to również aplikacja WPF, a jako funkcja bezpieczeństwa - klient może skonfigurować czas, w którym jego użytkownik zostanie wylogowany, jeśli będzie bezczynny.

Poniżej przedstawiono klasę, którą wykonałem w celu zawinięcia kodu wykrywania bezczynności (który wykorzystuje wbudowaną funkcjonalność systemu Windows).

Po prostu mamy zegar tykać przez 1 sekundę, aby sprawdzić, czy czas bezczynności jest większy niż określony próg ... zajmuje 0 CPU.

pierwsze, oto jak użyć kodu:

var idleTime = IdleTimeDetector.GetIdleTimeInfo(); 

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

można użyć tego, a także upewnić się, że WPF pełne ekranowane aplikacja jest „koncentruje się”, aby osiągnąć swoje potrzeby:

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

To małe piękno uratowało mnie przyzwoitą ilość kodowania, dziękuję. Korzystanie z wbudowanych funkcji systemu Windows to doskonała propozycja. – Digitalsa1nt

0

Implementuję rozwiązanie w klasie IdleDetector. Poprawiłem trochę kod. Detektor Iddle rzuca się w pole, które może zostać przechwycone! To daje! Czekam na komentarze.

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