5

一定時間非アクティブになったときに再起動する必要がある MVVM キオスク アプリケーションがあります。Prism と Unity を使用して、MVVM パターンを容易にしています。再起動を停止し、タイマーの処理方法も知っています。私が知りたいのは、アクティビティ、つまりマウス イベントがいつ発生したかを知る方法です。その方法を私が知っている唯一の方法は、メイン ウィンドウのプレビュー マウス イベントをサブスクライブすることです。それはMVVMの考えを壊しますよね?

これらのイベントをアプリケーションに公開するインターフェイスとしてウィンドウを公開することを考えましたが、それにはウィンドウがそのインターフェイスを実装する必要があり、MVVM も壊れているようです。

4

4 に答える 4

4

もう 1 つのオプションは、Windows API メソッドGetLastInputInfoを使用することです。

いくつかの警告

  • WPFなのでWindowsを想定しています
  • キオスクが GetLastInputInfo をサポートしているかどうかを確認する
  • 私はMVVMについて何も知りません。この方法はUIにとらわれない手法を使用しているため、うまくいくと思います。

使い方は簡単です。UserIdleMonitor.RegisterForNotification を呼び出します。通知メソッドと TimeSpan を渡します。ユーザー アクティビティが発生し、指定された期間停止すると、通知メソッドが呼び出されます。別の通知を受け取るには再登録する必要があり、いつでも登録を解除できます。49.7 日間 (および idlePeriod を含む) アクティビティがない場合、通知メソッドが呼び出されます。

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

更新しました

通知方法から再登録しようとするとデッドロックすることがある問題を修正しました。

符号なしの数学を修正し、未チェックで追加しました。

必要な場合にのみ通知を割り当てるように、タイマー ハンドラーがわずかに最適化されました。

デバッグ出力をコメントアウトしました。

DispatchTimer を使用するように変更されました。

登録解除機能を追加。

これはスレッドセーフではなくなったため、パブリック メソッドにスレッド チェックを追加しました。

于 2011-02-01T18:35:11.487 に答える
1

これは公式の回答ではありませんが、UserIdleMonitor興味のある人向けの私のバージョンは次のとおりです。

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 
}
于 2011-02-02T19:12:58.850 に答える
1

MVVM Light のEventToCommand 動作を使用して、MouseMove/MouseLeftButtonDown イベントをコマンドにリンクできます。これは本当に簡単なので、通常はブレンドで行われます。

ブレンドがない場合の xaml の例を次に示します。

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

ここで、 i:は Blend.Interactivity の xml 名前空間です。

于 2011-02-01T16:58:46.980 に答える
0

それを行う方法を私が知っている唯一の方法は、メインウィンドウのプレビューマウスイベントをサブスクライブすることです。それはMVVMの考えを壊しますね。

それは本当にあなたがそれをする方法に依存します。

このイベントにフックするビヘイビアーまたはアタッチされたプロパティを非常に簡単に記述し、それを使用してViewModelでICommandをトリガーすることができます。このようにして、基本的に「何かが起こった」イベントをVMにプッシュし、ビジネスロジックでこれを完全に処理できます。

于 2011-02-01T17:01:28.493 に答える