【问题标题】:WPF inactivity and activityWPF 不活动和活动
【发布时间】:2011-02-10 21:47:32
【问题描述】:

我正在尝试处理 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 在这里不是很有帮助。

【问题讨论】:

  • 该代码对我来说非常有用,并且当鼠标仍在Window 上时不会引发PreProcessInput。如果您只使用您发布的代码创建一个小应用程序,您会获得相同的效果吗?您使用的是哪个 .NET 版本?
  • @Meleak:谢谢!事实上,它只适用于上面的代码(我感到羞耻)。无论如何,在我的项目中,我仍然有那种奇怪的行为。我正在研究和缩小范围,并将提供更详细的信息。为了完整起见,我使用的是 .NET 4。
  • @Meleak:我已经更新了问题,以便实际理解行为。

标签: c# .net wpf events


【解决方案1】:

我们对我们的软件也有类似的需求...它也是一个 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;
    }
}

【讨论】:

  • 这个小美女为我节省了大量的编码,谢谢。使用 windows 内置函数是一个很好的建议。
  • @Timothy,在空闲 x 时间后,我还会显示一个警报框,以通知用户有关自动注销的信息。有什么方法可以在用户不空闲时重置该警报的计时器?截至目前,我需要为用户的每个活动重置警报时间。
  • 你的课效果很好。我这样使用它:在 windows_loaded 我启动了计时器: DispatcherTimer timer = new DispatcherTimer(); timer.Interval = TimeSpan.FromSeconds(1); timer.Tick += timer_Tick;计时器.Start();在 timer_tick 我调用这个空闲类并在 15 分钟后关闭应用程序: void timer_Tick(object sender, EventArgs e) { var idleTime = IdleTimeDetector.GetIdleTimeInfo(); if (idleTime.IdleTime.TotalMinutes >= 15) { this.Close(); } }
  • 由于 32 位刻度翻转,此解决方案将在一定的正常运行时间(约 50 天)后停止工作。此代码可能基于的 pinvoke 网站说:These samples do not take into account the rollover of the tick counter which will occur after ~50 days of uptime.This might be a potentially good workaround 这个问题。
  • 该类正在检查整个系统的空闲时间,而不仅仅是 wpf 应用程序。理想情况下,我想关闭应用程序,如果用户让我们说最后一个小时是在 ms 单词上,因此,wpf 没有集中。我该怎么做?
【解决方案2】:

我可以弄清楚是什么导致了所描述的行为。

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

可以通过在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();
        }
    }
}

【讨论】:

  • 如果我在 ViewModel 中使用它,因此无法访问 MainGrid(或表单元素),是否有另一种检测鼠标移动的方法?
  • 作为我上面评论的后续:我删除了整个if (e.StagingItem.Input is MouseMoveEventArgs) 语句,它对我来说工作正常,检测点击和鼠标移动,而忽略鼠标只是悬停在应用程序上。
【解决方案3】:

我在 IdleDetector 类中实现了该解决方案。我已经改进了一点代码。空闲检测器抛出一个可以被拦截的 IsIdle !它给了!我等一些cmets。

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

【讨论】:

    【解决方案4】:

    你试过PreviewMouseMove而不是听PreProcessInput吗?

    【讨论】:

    • 我相信这是一个更好的选择。唯一的问题是它不能从像PreProcessInput 这样的视图模型直接访问。相反,您必须在您的应用程序的MainWindow 上绑定它。
    猜你喜欢
    • 1970-01-01
    • 2011-07-31
    • 2014-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-20
    • 2014-08-22
    相关资源
    最近更新 更多