简体   繁体   English

WPF 不活动和活动

[英]WPF inactivity and activity

I'm trying to handle user inactivity and activity in a WPF application to fade some stuff in and out.我正在尝试处理 WPF 应用程序中的用户不活动和活动,以淡入淡出一些东西。 After a lot of research, I decided to go with the (at least in my opinion) very elegant solution Hans Passant posted here .经过大量研究,我决定使用(至少在我看来)非常优雅的解决方案 Hans Passant 在这里发布的 go 。

There's only one downside: As long as the cursor stays on top of the window, the PreProcessInput event gets continously fired.只有一个缺点:只要 cursor 保持在 window 之上, PreProcessInput事件就会持续触发。 I'm having a full-screen application, so this kills it.我有一个全屏应用程序,所以这会杀死它。 Any ideas how I can bypass this behaviour would be most appreciated.任何我如何绕过这种行为的想法都将不胜感激。

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

Update更新

I could narrow down the described behaviour better (see the rectangle1.Visibility update in the above code).我可以更好地缩小所描述的行为(参见上面代码中的rectangle1.Visibility更新)。 As long as the cursor rests on top of the window and for example the Visibility of a control is changed, the PreProcessInput is raised.只要 cursor 位于 window 的顶部,例如控件的Visibility发生更改,就会提高PreProcessInput Maybe I'm misunderstanding the purpose of the PreProcessInput event and when it fires.也许我误解了PreProcessInput事件的目的以及它何时触发。 MSDN wasn't very helpful here. MSDN 在这里不是很有帮助。

We've had a similar need for our software... it's a WPF application as well, and as a security feature - a client can configure a time that their user's will be logged off if they are idle. 我们对软件有类似的需求......它也是一个WPF应用程序,作为一个安全功能 - 客户端可以配置一个时间,如果用户空闲,他们将被注销。

Below is the class that I made to wrap the Idle Detection code (which utilizes built in Windows functionality). 下面是我用来封装空闲检测代码的类(它使用了内置的Windows功能)。

We simply have a timer tick ever 1 second to check if the idle time is greater than the specified threshold ... takes 0 CPU. 我们只需要1秒的计时器滴答来检查空闲时间是否大于指定的阈值...需要0 CPU。

First, here's how to use the code: 首先,这是如何使用代码:

var idleTime = IdleTimeDetector.GetIdleTimeInfo();

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

You can use this and also make sure that your WPF full screened app is "focused" to achieve your needs: 您可以使用此功能,并确保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;
    }
}

I could figure out what caused the described behaviour. 我可以弄清楚导致所述行为的原因。

For example when the Visibility of a control is changed, the PreProcessInput event is raised with PreProcessInputEventArgs.StagingItem.Input of the type InputReportEventArgs . 例如当Visibility的控制的改变时, PreProcessInput事件引发与PreProcessInputEventArgs.StagingItem.Input类型的InputReportEventArgs

The behaviour can be avoided by filtering the InputEventArgs for the types MouseEventArgs and KeyboardEventArgs in the OnActivity event and to verify if no mouse button is pressed and the position of the cursor is still the same as the application became inactive. 通过在OnActivity事件中过滤MouseEventArgsKeyboardEventArgs类型的InputEventArgs可以避免这种行为,并验证是否没有按下鼠标按钮,并且光标的位置仍然与应用程序变为非活动状态相同。

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

I implements the solution in a IdleDetector class. 我在IdleDetector类中实现了该解决方案。 I have improved a little bit the code. 我已经改进了一点代码。 The Iddle detector throw an IsIdle That can be intercepte ! Iddle探测器抛出一个可以拦截的IsIdle! It give that ! 它给了! I wait for some comments. 我等待一些评论。

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

您是否尝试过PreviewMouseMove而不是监听PreProcessInput

After digging in Martin Buberl code, I found what I think is a cleaner way to handle the PreProcessInput event for user activity.在深入研究 Martin Buberl 代码后,我发现了一种我认为更简洁的方式来处理用户活动的PreProcessInput事件。
I did it by checking the e.StagingItem.Input value for different mouse events and keyboard events:我通过检查不同鼠标事件和键盘事件的e.StagingItem.Input值来做到这一点:

private void InputManager_PreProcessInput(object sender, PreProcessInputEventArgs e)
{
    switch (e.StagingItem.Input)
    {
        //mouse button pressed
        case MouseButtonEventArgs mbea:
        //mouse whell used
        case MouseWheelEventArgs mwea:
        //generic mouse event for `MouseMove` event
        case MouseEventArgs mea when (mea.RoutedEvent.Name.Equals(Mouse.MouseMoveEvent.Name)):
        //KeyBoard event
        case KeyEventArgs kea:
            //TODO: manage activity notification
            break;
    }
}

or, without the switch statement:或者,没有 switch 语句:

private void InputManager_PreProcessInput(object sender, PreProcessInputEventArgs e)
{
    if (e.StagingItem.Input is MouseButtonEventArgs
        || e.StagingItem.Input is MouseWheelEventArgs
        || e.StagingItem.Input is KeyEventArgs
        || e.StagingItem.Input is MouseEventArgs mea && mea.RoutedEvent.Name.Equals(Mouse.MouseMoveEvent.Name)
        )
    {
        //TODO: manage activity notification
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM