以编程方式找出进程是否需要用户输入

5

我该如何以编程方式(使用C#)确定是否有另一个外部应用程序(原生应用程序,Java,.NET或其他...)正在要求用户输入?能否完全在托管代码中完成此操作?

我需要实现以下功能:

static Boolean IsWaitingForUserInput(String processName)
{
    ???
}

所谓要求用户输入,是指应用程序要求用户输入一些数据或退出错误消息(模态对话框),并且无法执行其正常任务。这里不包括等待用户绘制某些内容的绘图应用程序。

注意:为了反映底部评论并使问题更清晰,进行了编辑,因此某些评论和答案可能与问题不完全一致。在评估答案和备注时请考虑这一点。


你能在GUI应用程序中定义“等待用户输入”吗?鼠标点击不也是用户输入吗? - Lars Truijens
等待用户输入是指一切阻止第三方工具自动进行工作的事情。这包括等待退出对话框(例如大多数情况下的错误消息框)。 - jdehaan
我理解得没错吧,"第三方工具"和"另一个外部应用程序"都指的是同一个应用程序,你想要检查它的状态? - Michal Czardybon
没错!我思考了很久如何清晰地表达问题,人类沟通中总是有这么多问题,真是令人惊奇 :-) - jdehaan
我知道这并不是回答你的问题,但我相信你要寻找的应用程序状态的名称是“input-idle”。试着搜索一下看是否会有任何有帮助的结果。 - Christian Hayter
不是很准确,input-idle 是指应用程序开始处理输入消息的时间点。 - MSalters
4个回答

10

一般来说是不可能的。例如常见的应用程序之一,即文字处理器。现在它会在后台运行拼写检查,并定期自动保存您的文档等。然而,从用户的角度来看,它始终在等待输入。

另一个常见情况是幻灯片浏览器。在任何时刻,您都可以按一个键以前进一张幻灯片。但是您典型的用户不会将此视为“等待输入”。

总之,“等待输入”是主观状态,因此无法通过编程确定。


在一般情况下无法解决。应该采用一种策略进行模式应用;您需要确定如何检查每个应用程序以解决此问题,并找出什么构成“等待用户输入”状态。正如其他回答中所示,可以使用模态对话框要求用户输入来解决Excel的问题。 - Blue Toque
不可能的事情不存在;-) 我同意这是一个定义问题。我重新写了问题,更加精确,肯定有解决问题的方法,就像我发布的那个(最好的我能得到的,我很满意)。 - jdehaan
1
在大多数这类问题中,没有不可能的事情。只有“你有多想要它”的问题。 - Blue Toque
谢谢Oplopanax,有时我会想,一个明显不能帮助解决我和其他人的实际问题(虽然不好但至少比没有好)的答案为什么会得到这么多赞!!![在回答时,问题的描述方式存在误导,这不应该是指责MSalters的评论;-)] - jdehaan

4

你觉得这个怎么样?

我想出了一个解决方案,似乎可以工作,请在代码出现问题时通知我,以便我也能从改进中受益。据我测试,它适用于Excel。唯一让我不满意的问题是我不得不使用非托管调用。它还处理了基于对话框的应用程序的情况,例如MFC派生自CDialog。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;

namespace Util
{
    public class ModalChecker
    {
        public static Boolean IsWaitingForUserInput(String processName)
        {
            Process[] processes = Process.GetProcessesByName(processName);
            if (processes.Length == 0)
                throw new Exception("No process found matching the search criteria");
            if (processes.Length > 1)
                throw new Exception("More than one process found matching the search criteria");
            // for thread safety
            ModalChecker checker = new ModalChecker(processes[0]);
            return checker.WaitingForUserInput;
        }

        #region Native Windows Stuff
        private const int WS_EX_DLGMODALFRAME = 0x00000001;
        private const int GWL_EXSTYLE = (-20);
        private delegate int EnumWindowsProc(IntPtr hWnd, int lParam);
        [DllImport("user32")]
        private extern static int EnumWindows(EnumWindowsProc lpEnumFunc, int lParam);
        [DllImport("user32", CharSet = CharSet.Auto)]
        private extern static uint GetWindowLong(IntPtr hWnd, int nIndex);
        [DllImport("user32")]
        private extern static uint GetWindowThreadProcessId(IntPtr hWnd, out IntPtr lpdwProcessId);
        #endregion

        // The process we want the info from
        private Process _process;
        private Boolean _waiting;

        private ModalChecker(Process process)
        {
            _process = process;
            _waiting = false; //default
        }

        private Boolean WaitingForUserInput
        {
            get
            {
                EnumWindows(new EnumWindowsProc(this.WindowEnum), 0);
                return _waiting;
            }
        }

        private int WindowEnum(IntPtr hWnd, int lParam)
        {
            if (hWnd == _process.MainWindowHandle)
                return 1;
            IntPtr processId;
            GetWindowThreadProcessId(hWnd, out processId);
            if (processId.ToInt32() != _process.Id)
                return 1;
            uint style = GetWindowLong(hWnd, GWL_EXSTYLE);
            if ((style & WS_EX_DLGMODALFRAME) != 0)
            {
                _waiting = true;
                return 0; // stop searching further
            }
            return 1;
        }
    }
}

2
这可能是“等待用户输入”的唯一有效定义,即模态对话框。我们应该称其为“要求用户输入”。 - slf
好主意!我知道这或多或少是定义的问题。这段代码有望成为其他想做同样事情的人的良好基础。 - jdehaan
其中一个缺点是它无法检测到触发的JIT调试器...有任何解决办法吗(不仅适用于一种特殊的JIT调试器)? - jdehaan

0

如果我理解得正确,您可以尝试枚举进程的线程并检查它们的状态。Windows任务管理器也会做类似的事情。但是,这将需要Win32函数-Thread32First和Thread32Next等等-但您可以通过在C#中最简单地使用P/Invoke来实现此目的:

    [DllImport("Executor.dll")]
    public static extern bool Thread32First(IntPtr handle, IntPtr threadEntry32);

(精确的签名可能会有所不同)。

编辑:好的,在.NET库中有相应的函数。


枚举线程并检查它们的状态是不够的,正如MSalters所指出的那样,Input-Idle是应用程序处理消息的状态,不能真正100%地表示用户需要输入。 - jdehaan

0
如果可能的话,请将其他代码重写为并发输入处理器(类似于并发Web服务器的算法):
Wait for input
Fork process
  Parent: Repeat
  Child: (Worker) handle input

当然,你仍然可以拥有你的函数:
static Boolean IsWaitingForUserInput(String processName) {
    return true;
}

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