当用户更改活动进程时触发事件

5
是否有一种事件或方法可以在程序用户将活动窗口切换到另一个进程的窗口时触发?
如果没有这样的事件,最好的方法是什么?
我目前使用一个定时器,每3秒运行Process.GetCurrentProcess(),但我正在寻找更好和更高效的方法来完成它,而且我不想降低间隔时间,因为会增加程序消耗的资源或检查活动进程所需的时间太长。
我知道有很多Windows内置功能基本上是隐藏的,我不了解足够的知识,因此如果有人有任何类似的想法,如果您能帮助我,那就太好了。

我不知道你所说的获取当前进程是什么意思。你是指当前活动窗口吗?因为你的电脑上有多个进程在不断运行。 ;) - Fabian Bigler
详细阐述其原因。例如,如果您只关心失去焦点,那么您可以对其做出反应。 - DonBoitnott
这真的取决于你想要实现什么,你想做什么? - James
你正在构建一个多进程应用程序吗?Process.GetCurrentProcess()将返回调用该方法的当前进程。我没有使用过多进程应用程序,因此没有足够的词汇来表达,因为首先需要理解一些重要术语... - King King
2
你可以使用Windows Hooks来实现这一点,参见.NET/Win32 - event to detect when a window belonging to another app gets focus - James
显示剩余4条评论
2个回答

8

这里需要用到的API是SetWinEventHook。你只需在你的应用程序启动时使用正确的选项调用它,就可以开始接收到有关用户从桌面上正在运行的任何进程切换焦点的通知。

[DllImport("user32.dll", SetLastError = true)]
internal static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventProc lpfnWinEventProc, int idProcess, int idThread, uint dwflags);
[DllImport("user32.dll")]
internal static extern int UnhookWinEvent(IntPtr hWinEventHook);
internal delegate void WinEventProc(IntPtr hWinEventHook, uint iEvent, IntPtr hWnd, int      idObject, int idChild, int dwEventThread, int dwmsEventTime);

const uint WINEVENT_OUTOFCONTEXT = 0;
const uint EVENT_SYSTEM_FOREGROUND = 3;
private IntPtr winHook;
private WinEventProc listener;

public void StartListeningForWindowChanges()
{
    listener = new WinEventProc(EventCallback);
    //setting the window hook
    winHook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, listener, 0, 0, WINEVENT_OUTOFCONTEXT);
}

public void StopListeningForWindowChanges()
{
    UnhookWinEvent(winHook);
}

private static void EventCallback(IntPtr hWinEventHook, uint iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime)
{
    // handle active window changed!
}

1
@stripe103 抱歉,那是我的错,我在签名中混合使用了 int/uint,并且还有一个旧的枚举类型。我已经更新了答案,在 API 要求使用 uint 的地方一致使用它。 - James
1
@stripe103 不,上面的代码应该是可以工作的。只是出于好奇,你是在控制台应用程序中运行它吗?你的应用程序需要运行一个消息泵(控制台窗口没有)。尝试在WinForms应用程序中运行它,如果还是不起作用,请告诉我。 - James
@stripe103 哦,好的,我知道问题出在哪里了。我正在使用 INCONTEXT 标志,这意味着回调必须在它自己的 DLL 中,你需要使用 OUTOFCONTEXT - 请查看更新的答案。 - James
太棒了,现在至少在更改活动窗口时会发生一些事情。但是,这是一个错误。当EventCallback()完成时,它会给我这个错误: “在类型为'(Project name)!(Project Name).mainForm+WinEventProc::Invoke'的垃圾回收委托上进行了回调。这可能会导致应用程序崩溃、损坏和数据丢失。当将委托传递给非托管代码时,必须由托管应用程序保持其存活状态,直到可以保证永远不会调用它们。”。 - stripe103
1
没问题 :) SetWinEventHook 可以用于捕获各种 事件。在 API 调用中,您需要传入一个范围。在我的示例中,我们将该范围限制为只捕获 EVENT_SYSTEM_FOREGROUND,这只会在前台窗口发生变化时通知您,即当一个新窗口获得焦点时。请记住,这将发生在每个窗口而不是每个进程中,因此在同一进程中切换窗口时,您将接收到多个通知。 - James
显示剩余7条评论

1
我会说其中一种方法是使用Windows钩子,即CBT钩子。它更接近“旧好”的Win32 API和通用的C / C ++人员。同时,据我所见,也有C#版本http://msdn.microsoft.com/en-us/magazine/cc188966.aspx#S5
因此,想法是每次获得HCBT_ACTIVATE类型的钩子事件(要激活的窗口)时检查当前进程,然后检查拥有该窗口的进程。如果它与先前记住的进程不匹配-现在是触发事件的时候了。唯一需要记住的是,您自己的操作也可能触发CBT钩子(您必须避免无限自推动循环!)。

嗯...我不太明白你的意思,因为这超出了我的知识范围,但我会进行一些研究。感谢你的回答!看起来没问题,所以我会将其标记为答案 =) - stripe103

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