Подтвердить что ты не робот

Неактивность и активность WPF

Я пытаюсь обрабатывать бездействие и активность пользователя в приложении WPF, чтобы угаснуть некоторые вещи. После многих исследований я решил пойти с (по крайней мере, на мой взгляд) очень элегантным решением, которое Ханс Пассант опубликовал здесь.

Есть только один недостаток: пока курсор остается в верхней части окна, событие PreProcessInput непрерывно запускается. У меня полноэкранное приложение, так что это убивает его. Любые идеи о том, как я могу обойти это поведение, будут наиболее ценными.

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

Обновление

Я мог бы сузить описанное поведение лучше (см. обновление rectangle1.Visibility в приведенном выше коде). Пока курсор лежит поверх окна и, например, изменяется Visibility элемента управления, появляется PreProcessInput. Возможно, я не понимаю цель события PreProcessInput и когда он срабатывает. MSDN здесь не очень помог.

4b9b3361

Ответ 1

Я мог бы выяснить, что вызвало описанное поведение.

Например, при изменении Visibility элемента управления событие PreProcessInput создается с помощью PreProcessInputEventArgs.StagingItem.Input типа InputReportEventArgs.

Поведение можно избежать, отфильтровав InputEventArgs для типов MouseEventArgs и KeyboardEventArgs в событии OnActivity и чтобы проверить, не нажата ли кнопка мыши, и положение курсора по-прежнему совпадает с положением приложение стало неактивным.

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

Ответ 2

У нас была аналогичная потребность в нашем программном обеспечении... это приложение WPF, а также как функция безопасности - клиент может настроить время, когда их пользователь будет отключен, если они не работают.

Ниже приведен класс, который я сделал для переноса кода Idle Detection (который использует встроенные функции Windows).

У нас просто есть отметка таймера за 1 секунду, чтобы проверить, превышает ли время простоя заданное пороговое значение... принимает 0 CPU.

Во-первых, здесь, как использовать код:

var idleTime = IdleTimeDetector.GetIdleTimeInfo();

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

Вы можете использовать это, а также убедиться, что ваше полноэкранное приложение WPF "сфокусировано" для достижения ваших потребностей:

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

Ответ 3

Вместо того, чтобы слушать PreProcessInput, вы пробовали PreviewMouseMove?

Ответ 4

Я реализую решение в классе IdleDetector. Я немного улучшил код. Детектор Iddle бросает IsIdle. Это может быть перехват! Это так! Я жду некоторых комментариев.

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