WPF不活动和活动

时间:2011-02-10 21:47:32

标签: c# .net wpf events

我正在尝试处理WPF应用程序中的用户不活动和活动,以淡入淡出某些内容。经过大量的研究,我决定采用(至少在我看来)非常优雅的解决方案Hans Passant发布here

只有一个缺点:只要光标停留在窗口顶部,就会不断触发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在这里不是很有帮助。

4 个答案:

答案 0 :(得分:41)

我们对软件也有类似的需求......它也是一个WPF应用程序,作为安全功能,客户端可以配置一个时间,如果用户空闲,他们将被注销。

下面是我用来封装空闲检测代码的类(它使用了内置的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;
    }
}

答案 1 :(得分:19)

我可以弄清楚导致所述行为的原因。

例如,当控件的Visibility发生更改时,PreProcessInput事件会引发PreProcessInputEventArgs.StagingItem.Input类型的InputReportEventArgs

可以通过在InputEventArgs事件中为MouseEventArgsKeyboardEventArgs类型过滤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 :(得分:1)

您尝试PreviewMouseMove

而不是听PreProcessInput

答案 3 :(得分:1)

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