5 Stimmen

Wie kann ich Inaktivität in einer MVVM-Anwendung feststellen?

Ich habe eine MVVM-Kioskanwendung, die ich neu starten muss, wenn sie für eine bestimmte Zeitspanne inaktiv gewesen ist. Ich verwende Prism und Unity, um das MVVM-Muster zu erleichtern. Ich habe den Neustart im Griff und weiß sogar, wie ich den Timer handhaben muss. Was ich wissen möchte, ist, wie man weiß, wann eine Aktivität, d. h. ein Mausereignis, stattgefunden hat. Der einzige Weg, den ich kenne, ist das Abonnieren der Vorschau-Mausereignisse des Hauptfensters. Das bricht den MVVM-Gedanken, nicht wahr?

Ich habe darüber nachgedacht, mein Fenster als eine Schnittstelle, die diese Ereignisse zu meiner Anwendung ausstellt, aber das würde erfordern, dass das Fenster diese Schnittstelle implementieren, die auch MVVM zu brechen scheint.

4voto

Tergiver Punkte 13761

Eine weitere Möglichkeit ist die Verwendung der Windows-API-Methode GetLastInputInfo .

Einige Hohlräume

  • Ich nehme an, Windows, weil es WPF ist
  • Prüfen Sie, ob Ihr Kiosk GetLastInputInfo unterstützt
  • Ich weiß nichts über MVVM. Diese Methode verwendet eine Technik, die UI agnostic ist, so würde ich denken, es würde für Sie arbeiten.

Die Verwendung ist einfach. Rufen Sie UserIdleMonitor.RegisterForNotification auf. Sie geben eine Benachrichtigungsmethode und eine Zeitspanne an. Wenn eine Benutzeraktivität auftritt und dann für den angegebenen Zeitraum aufhört, wird die Benachrichtigungsmethode aufgerufen. Sie müssen sich erneut registrieren, um eine weitere Benachrichtigung zu erhalten, und können die Registrierung jederzeit aufheben. Wenn 49,7 Tage lang (plus die idlePeriod) keine Aktivität stattfindet, wird die Benachrichtigungsmethode aufgerufen.

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

Aktualisiert

Es wurde ein Problem behoben, bei dem es zu einem Deadlock kommen konnte, wenn man versuchte, sich über die Benachrichtigungsmethode neu zu registrieren.

Vorzeichenlose Mathematik korrigiert und ungeprüft hinzugefügt.

Leichte Optimierung im Timer-Handler, um Benachrichtigungen nur bei Bedarf zuzuweisen.

Die Debugging-Ausgabe wurde auskommentiert.

Geändert, um DispatchTimer zu verwenden.

Die Möglichkeit zur Aufhebung der Registrierung wurde hinzugefügt.

Thread-Prüfungen in öffentlichen Methoden hinzugefügt, da diese nicht mehr thread-sicher sind.

1voto

Dave Arkell Punkte 3760

Sie könnten vielleicht verwenden MVVM Light's EventToCommand-Verhalten, um das MouseMove/MouseLeftButtonDown-Ereignis mit einem Befehl zu verknüpfen. Dies wird normalerweise in Blend gemacht, weil es wirklich einfach ist.

Hier ist ein Beispiel xaml, wenn Sie nicht Blend haben:

<Grid>
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="MouseLeftButtonDown">
      <GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding theCommand} />
    </i:EventTrigger>
  </i:Interaction.Triggers>
</Grid>

Wo i: ist ein xml-Namespace für Blend.Interactivity.

1voto

Jordan Punkte 9236

Dies ist keine offizielle Antwort, aber hier ist meine Version von UserIdleMonitor für alle, die daran interessiert sind:

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 
}

0voto

Reed Copsey Punkte 536986

Die einzige Möglichkeit, die ich kenne, ist das Abonnieren der Vorschau-Mausereignisse des Hauptfensters. Das bricht MVVM Gedanken, nicht wahr?

Das hängt wirklich davon ab, wie man es macht.

Sie könnten ganz einfach ein Verhalten oder eine angehängte Eigenschaft schreiben, die Sie in dieses Ereignis einhaken und es verwenden, um einen ICommand in Ihrem ViewModel auszulösen. Auf diese Weise schieben Sie im Grunde ein "Etwas ist passiert"-Ereignis in die VM, wo Sie dies vollständig in Ihrer Geschäftslogik behandeln können.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X