【问题标题】:How to detect a Winforms app has been idle for certain amount of time如何检测 Winforms 应用程序已空闲一段时间
【发布时间】:2016-04-05 15:33:26
【问题描述】:

检测 C# Winforms 应用程序是否空闲一段时间的最佳方法是什么?

如果用户决定按 ALT+TAB 键并使用 Microsoft Word 或其他任何东西 30 分钟,让我们的应用程序闲置,我希望我们的应用程序自行终止。

这是类似问题的公认答案: Check if an application is idle for a time period and lock it

但是,答案与 Windows 闲置一段时间有关,不是特定的应用程序。我希望我们的应用程序在 30 分钟内未使用时终止。

我看了这个:

http://www.codeproject.com/Articles/13756/Detecting-Application-Idleness

但是,我在 cmets 中了解到,这不适用于多线程应用程序,我们的应用程序就是其中之一。 我们的应用有一个主窗体,它生成模态和非模态窗体,它们使用 Async Await 来填充网格等。

然后我查看了 SetWindowsHookEx,不确定这是否可行。

肯定有人有解决方案(希望与 .NET 4.5 兼容):)

TIA

【问题讨论】:

  • Winform onfocus lost 可以与定时器结合使用。
  • 您需要使用性能计数器来衡量它,然后根据您认为您的应用程序处于空闲状态的数字来决定
  • 如果我看到一个应用程序自行终止,我会认为它崩溃了,这不会给人留下积极的印象......你确定这是你想要的行为吗?

标签: c# .net winforms


【解决方案1】:

有很多方法可以做到这一点,答案在某种程度上取决于您需要做什么。你对你需要什么很清楚和具体。以下是我开发的可能符合您要求的内容。它所做的是使用 Application.Idle 来确定应用程序何时完成处理消息,然后它设置一个计时器并过滤(侦听)应用程序的所有消息,如果收到相关消息(例如鼠标或键盘),那么它重置计时器。它忽略鼠标移动,因为可以在不使用应用程序的情况下将鼠标移动到应用程序上。自从我写那篇文章以来已经有一段时间了,所以我不确定细节,但如果有必要我可以弄清楚。请注意,这是一个控制台程序,使示例更易于尝试,但代码是为表单应用程序设计的。

using System;
using System.Security.Permissions;
using System.Windows.Forms;

namespace _121414
{
    static class Program
    {
        public static Timer IdleTimer = new Timer();
        const int MinuteMicroseconds = 60000;
        static Form1 f = null;

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            LeaveIdleMessageFilter limf = new LeaveIdleMessageFilter();
            Application.AddMessageFilter(limf);
            Application.Idle += new EventHandler(Application_Idle);
            IdleTimer.Interval = MinuteMicroseconds;    // One minute; change as needed
            IdleTimer.Tick += TimeDone;
            IdleTimer.Start();
            f = new Form1();
            Application.Run(f);
            Application.Idle -= new EventHandler(Application_Idle);
        }

        static private void Application_Idle(Object sender, EventArgs e)
        {
            if (!IdleTimer.Enabled)     // not yet idling?
                IdleTimer.Start();
        }

        static private void TimeDone(object sender, EventArgs e)
        {
            IdleTimer.Stop();   // not really necessary
            MessageBox.Show("Auto logoff");
            f.Close();
        }

    }

    [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
    public class LeaveIdleMessageFilter : IMessageFilter
    {
        const int WM_NCLBUTTONDOWN = 0x00A1;
        const int WM_NCLBUTTONUP = 0x00A2;
        const int WM_NCRBUTTONDOWN = 0x00A4;
        const int WM_NCRBUTTONUP = 0x00A5;
        const int WM_NCMBUTTONDOWN = 0x00A7;
        const int WM_NCMBUTTONUP = 0x00A8;
        const int WM_NCXBUTTONDOWN = 0x00AB;
        const int WM_NCXBUTTONUP = 0x00AC;
        const int WM_KEYDOWN = 0x0100;
        const int WM_KEYUP = 0x0101;
        const int WM_MOUSEMOVE = 0x0200;
        const int WM_LBUTTONDOWN = 0x0201;
        const int WM_LBUTTONUP = 0x0202;
        const int WM_RBUTTONDOWN = 0x0204;
        const int WM_RBUTTONUP = 0x0205;
        const int WM_MBUTTONDOWN = 0x0207;
        const int WM_MBUTTONUP = 0x0208;
        const int WM_XBUTTONDOWN = 0x020B;
        const int WM_XBUTTONUP = 0x020C;

        // The Messages array must be sorted due to use of Array.BinarySearch
        static int[] Messages = new int[] {WM_NCLBUTTONDOWN,
            WM_NCLBUTTONUP, WM_NCRBUTTONDOWN, WM_NCRBUTTONUP, WM_NCMBUTTONDOWN,
            WM_NCMBUTTONUP, WM_NCXBUTTONDOWN, WM_NCXBUTTONUP, WM_KEYDOWN, WM_KEYUP,
            WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN, WM_RBUTTONUP,
            WM_MBUTTONDOWN, WM_MBUTTONUP, WM_XBUTTONDOWN, WM_XBUTTONUP};

        public bool PreFilterMessage(ref Message m)
        {
            if (m.Msg == WM_MOUSEMOVE)  // mouse move is high volume
                return false;
            if (!Program.IdleTimer.Enabled)     // idling?
                return false;           // No
            if (Array.BinarySearch(Messages, m.Msg) >= 0)
                Program.IdleTimer.Stop();
            return false;
        }
    }
}

【讨论】:

  • 我在此基础上构建了一些东西。 SecurityPermission 属性是做什么用的?在 MSDN 网站上它说 This API is now obsolete 没有它似乎可以工作,并且不需要项目的“允许不安全代码”
  • @t0b4cc0 你需要询问微软。他们倾向于在没有解释的情况下说事情已经过时,有时文档并没有过时。但是,在这种情况下,我认为您只需向下滚动到页面中的备注即可。
  • 谢谢!我注意到当我调用“MessageBox.Show”时,空闲计时器不起作用(即消息框打开时没有超时)。所以我添加了一个包装器,它在显示消息框之前调用 IdleTimer.Start()。
【解决方案2】:

您需要确定“空闲”的含义。我假设在要求的时间内应用程序没有收到鼠标或键输入。

您可以添加一个消息过滤器来查看所有应用程序范围的 Windows 消息。见Application.AddMessageFilter。检查 key down 和 mouse down 的消息代码,并记录它们最后一次发生的时间,但返回 false 以便运行时正常处理所有消息。可能的代码是:

WM_LBUTTONDOWN = 513
RBUTTONDOWN = 516
WM_KEYDOWN = 256

然后在其他地方的一个单独的计时器中检查最后一个事件何时超过 30 分钟。

“终止”应用程序也存在潜在问题。如果应用程序打开了模态表单,听起来好像它可能不是一个特别安全的状态来突然杀死。

【讨论】:

  • IIRC - 您可能会遇到问题,因为消息过滤器是线程特定的。不确定这是否适用于 OP,因为他明确表示他需要一些可以跨多线程应用程序工作的东西。
  • 虽然 OP 正在其他线程上运行后台任务,但我的理解是 Windows 消息仍会到达原始线程上
  • 我提供了一个样本。试一试会很容易。大多数线程没有 UI(消息循环)。消息过滤器可能需要用于拥有自己的 UI(消息循环)的每个其他线程。如果线程中可能存在非 UI 活动的相关活动,则需要单独检测。
猜你喜欢
  • 2022-01-19
  • 1970-01-01
  • 1970-01-01
  • 2011-06-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-03
相关资源
最近更新 更多