WPF:应用程序空闲时间

10

我需要计算我的WPF应用程序的空闲时间(空闲时间=当没有键盘输入,鼠标输入(移动+点击)发生时)。

到目前为止,我尝试了两种方法,但似乎都不起作用:

  1. 使用Dispatcher在每次获取contextIdle优先级时调用一个委托,问题是绑定和许多其他操作也会调用它,因此我无法真正使用它。
  2. 使用Input Manager,我注册了“System.Windows.Input.InputManager.Current.PostProcessInput”事件,并且每次被调用时重新启动了空闲时间计数。第二种方法似乎很有前途,但问题是当鼠标位于应用程序上方(它具有焦点)时,我仍然会收到该事件。

还有其他想法吗?或者也许可以修改第二个解决方案使其起作用?

2个回答

13

我使用了几种不同的技术来解决问题,最终得到了一个相当不错的解决方案。我使用GetLastInput来计算系统上次被触摸的时间。这在其他地方都有很好的文档,但这是我的方法:

public static class User32Interop
{
    public static TimeSpan GetLastInput() 
    { 
        var plii = new LASTINPUTINFO();
        plii.cbSize = (uint)Marshal.SizeOf(plii); 
        if (GetLastInputInfo(ref plii))       
            return TimeSpan.FromMilliseconds(Environment.TickCount - plii.dwTime); 
        else       
            throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); 
    }

    [DllImport("user32.dll", SetLastError = true)]
    static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);
    struct LASTINPUTINFO { 
        public uint cbSize; 
        public uint dwTime;   
    }
}

这只告诉我系统何时处于闲置状态,而不是应用程序。如果用户点击Word并在那里工作一个小时,我仍然想要超时。为了处理这种情况,我只需通过覆盖应用程序对象上的OnDeactivated和OnActivated方法来记住我的应用程序何时失去焦点:

    override protected void OnDeactivated(EventArgs e)
    {
        this._lostFocusTime = DateTime.Now;
        base.OnDeactivated(e);
    }

    protected override void OnActivated(EventArgs e)
    {
        this._lostFocusTime = null;
        base.OnActivated(e);
    }

我将IsIdle例程添加到应用程序对象中。它处理应用程序具有焦点但没有任何操作发生的全局情况(IsMachineIdle),以及在用户做其他事情时应用程序失去焦点的特定情况(isAppIdle):

    public bool IsIdle
    {
        get
        {
            TimeSpan activityThreshold = TimeSpan.FromMinutes(1);
            TimeSpan machineIdle = Support.User32Interop.GetLastInput();
            TimeSpan? appIdle = this._lostFocusTime == null ? null : (TimeSpan?)DateTime.Now.Subtract(_lostFocusTime.Value);
            bool isMachineIdle = machineIdle > activityThreshold ;
            bool isAppIdle = appIdle != null && appIdle > activityThreshold ;
            return isMachineIdle || isAppIdle;
        }
    }

我做的最后一件事是创建一个定时器循环,每隔几秒钟轮询此标志事件。

这似乎运作良好。


请注意,_lostFocusTime 应声明为 DateTime? 变量。如果您尝试声明为 DateTime,则无法将值设置为 NULL。 - Jesse
Environment.TickCount的类型为int,每24.9天强制包装一次。LASTINPUTINFO.dwTime的类型为uint,因此直到49.7天才会包装。如果机器已经运行超过24.9天,则示例中的数学计算将出现问题。我通过将Environment.TickCount替换为调用WINAPI函数GetTickCount来缓解这个问题,该函数也返回一个uint值。如果上一次输入是在49.5天,当前滴答计数是在50.0天,我不确定会发生什么。根据对另一个问题的回答,应该没问题。 (https://dev59.com/8HVC5IYBdhLWcg3wnCSA#1078089) - Tony Pulokas

5

看起来没有人回答,所以我继续查找并找到了一个相对简单的解决方案,使用操作系统的最后输入时间和运行时间。代码非常简单,但这个解决方案让我进行了数据轮询,这是我从不建议的,并且它不是在应用程序级别而是在操作系统级别,这不是我需要的确切解决方案。 如果有人打开这个线程,这就是代码,只需使用GetIdleTime():

 public class IdleTimeService
{
    //Importing the Dll & declaring the necessary function
    [DllImport("user32.dll")]
    private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);


    /// <summary>
    /// return the current idle time (in ms)
    /// </summary>
    /// <returns>current idle time in ms</returns>
    public static int GetIdleTime()
    {

        //Creating the object of the structure
        LASTINPUTINFO lastone = new LASTINPUTINFO();

        //Initialising  
        lastone.cbSize = (uint)Marshal.SizeOf(lastone);
        lastone.dwTime = 0;

        int idleTime = 0;

        //To get the total time after starting the system.
        int tickCount = System.Environment.TickCount;

        //Calling the dll function and getting the last input time.
        if (GetLastInputInfo(ref lastone))
        {
            idleTime = tickCount - (int)lastone.dwTime;
            return idleTime;
        }
        else
            return 0;
    }


}

你好,我正在尝试解决您的原始问题,但遇到了与您的解决方案相同的问题:它是系统范围的。您最终解决了如何进行应用程序范围内的空闲检测吗? - dave
不是很清楚,我还在卡在系统解决方案上,但如果你能找到解决方案的话,能否发帖分享一下就太好了。 - Clueless
我将我的解决方案作为答案添加了进去。如果您遇到任何问题或认为它很愚蠢,请告诉我。 - dave

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接