如何获取应用程序的活动子窗口?
我有这个问题:我有一个特定应用程序的 mainWindow 的处理程序,并且我想在该应用程序上模拟按键...
我正在使用 sendMessage/postMessage api 调用来执行此操作。 我不使用 .Net SendKeys 函数或 win32 api 的 keybd_event 的原因是它们在全局级别模拟按键。 就我而言,目标应用程序不是最活跃的应用程序(其他应用程序可能在更高的 z 级别上运行,因此覆盖了目标应用程序)。
sendMessage 和 postMessage 的问题是您必须传递您想要按下按键的确切子窗口的处理程序。 例如,在记事本中,如果我将密钥发送到主窗口的处理程序,则不会发生任何事情,我必须将密钥发送到子窗口的处理程序,该子窗口基本上由可以在其中书写的白色画布组成。
获取活动子窗口的处理程序是问题所在。 一开始,我使用 GetTopWindow 或 GetWindow(GW_CHILD) api 调用,因为它返回最活跃的子窗口。 我所做的就是继续调用 GetWindow(GW_CHILD) 直到我得到一个不再有 childWindows 的子窗口。 这对于记事本或绘画等某些应用程序来说效果很好。 但是,在某些情况下(例如 Firefox),它不起作用。 原因是父窗口拥有整个 Firefox 区域,而其子窗口拥有打开的网页(如 google)。 因此,当我请求 mainWindow 的最活跃子窗口时,它返回它拥有的唯一子窗口,即与网页区域相对应的子窗口。 仅当活动窗口是该窗口时(例如用户在某一页面的文本框中写入内容),它才有效。 但是,如果活动的是地址栏,那么它就不起作用,因为活动窗口不是子窗口,而是实际上的父窗口......而且我无法以编程方式获取此信息。
我实际上找到了一种方法,使用 GetGUIThreadInfo api 调用,使用以下代码:
// get thread of the main window handle of the process
var threadId = GetWindowThreadProcessId(firefox.MainWindowHandle, IntPtr.Zero);
// get gui info
var info = new GUITHREADINFO();
info.cbSize = (uint)Marshal.SizeOf(info);
if (!GetGUIThreadInfo(threadId, out info))
throw new Win32Exception();
// send the letter W to the active window
PostMessage(info.hwndActive, WM_KEYDOWN, (IntPtr)Keys.W, IntPtr.Zero);
它工作得很好:如果地址栏处于活动状态,它会向地址栏发送一个“W”字母。 如果谷歌的搜索文本框处于活动状态,它会向其发送“W”字母......完美! 然而,这个方法我不能使用,原因很简单: 如果目标应用程序不是操作系统的活动窗口,则 ThreadInfo 结构将为空。 例如,如果我的目标是 Firefox,那么如果 Firefox 处于活动状态(最顶层的应用程序,聚焦/活动的应用程序),它就可以工作,但是如果记事本位于 Firefox 之上,那么它就不起作用,它无法获取活动窗口处理程序。
我知道我可以通过使用 setForegroundWindow api 调用来激活目标应用程序,然后捕获活动子窗口的处理程序来解决此问题,但我不想将目标应用程序带到前台。
我还尝试过其他技术,例如 AttachThreadInput() 和 GetFocus() api 调用,它们也有效,但存在相同的问题:如果目标应用程序不是活动的 Windows 应用程序,则它不起作用。
因此,基本上我需要找到某种方法将处理程序获取到应用程序的活动子窗口,即使该应用程序不是最活跃的应用程序。
有任何想法吗? 谢谢
I have this problem: I have an handler to the mainWindow of a certain application, and I want to simulate a keypress on that application...
I'm using sendMessage/postMessage api calls to do this. The reason why I don't use the .Net SendKeys function or the keybd_event of the win32 api, is that they simulate the keypress at a global level. In my case, the target application is not the top-active one (other application may be running in a higher z-level, hence covering the target app).
The problem with sendMessage and postMessage is that you must pass the handler of the exact childwindow where you want the key to be pressed. For example, in notepad, if I send the key to the handler of the mainWindow, nothing happens, I have to send the key to the handler of the child window that basically consists of the white canvas where you can write.
Obtaining the handler to the active child window is the problem. In the beginning, I was using the GetTopWindow or GetWindow(GW_CHILD) api calls, as it returns the most active child window. What I was doing was to keep calling the GetWindow(GW_CHILD) until I got a childwindow that had no more childWindows. This works ok for some applications like notepad or paint. However, in some cases (like firefox for example), it doesn't work. The reason for that is that the parent window has the whole firefox area, and its childwindow has the opened WebPage (like google). So, when I ask for the most active child window of the mainWindow, it returns the only child window it has, which is the one corresponding to the Webpage area. It only works if the active window is that one (like if the user is writing something on a textbox of a certain page). But if what is active is, let's say, the address bar, it doesn't work because the active window is not the child window but actually the parent... and I can't get this information programatically.
I actually found a way of doing this, using the GetGUIThreadInfo api call, using the following code:
// get thread of the main window handle of the process
var threadId = GetWindowThreadProcessId(firefox.MainWindowHandle, IntPtr.Zero);
// get gui info
var info = new GUITHREADINFO();
info.cbSize = (uint)Marshal.SizeOf(info);
if (!GetGUIThreadInfo(threadId, out info))
throw new Win32Exception();
// send the letter W to the active window
PostMessage(info.hwndActive, WM_KEYDOWN, (IntPtr)Keys.W, IntPtr.Zero);
And it works very well: If the address bar is active, it sends a "W" letter to the address bar. If the search textBox of google is active, it sends the "W" letter to it... Perfect! However, this method can't be used by me for a simple reason:
If the target application is not the active window of the operating system, the ThreadInfo structure comes empty. For example, if I'm targetting firefox, it works if firefox is active (the top-most application, the focused/active one), but if, let's say, notepad is on top of firefox, it doesn't work, it is unable to get the active window handler.
I know I can solve this by using the setForegroundWindow api call to activate the target application and then capture the handler of the active child-window but I didn't want to have to bring the target app to the foreground.
I've also tried other techniques like AttachThreadInput() and GetFocus() api calls, which also works, but has the same problem: If the target application is not the active windows application, it doesn't work.
So basically I need to find some way of getting the handler to the active childwindow of an application even if that application is not the top-active one.
Any ideas?
Thanks
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您可能想查看 EnumChildWindows 函数。
You might want to check out the EnumChildWindows function.
如果其他一切都失败了,这里有另一个想法:您可能需要考虑使用 WH_CBT 或 WH_CALLWNDPROC 挂钩来监视目标线程的哪个子窗口最后获得焦点。
安装 CBT 挂钩 (WH_CBT) 并侦听 HCBT_SETFOCUS 通知。
或者使用 WH_CALLWNDPROC 挂钩并侦听 WM_SETFOCUS 消息。
不要在钩子过程中做太多事情,否则会占用系统资源。 只需保存所需的信息并为自己发布自定义消息以供稍后处理。
If everything else fails, here is another idea: You might want to consider using a WH_CBT or a WH_CALLWNDPROC hooks to monitor which child window of the target thread has been focused last.
Install a CBT hook (WH_CBT) and listen for the HCBT_SETFOCUS notification.
Or use a WH_CALLWNDPROC hook and listen for the WM_SETFOCUS message.
Don't do much in the hook proc or you'll hog down the system resources. Just save the needed information and post yourself a custom message to process it later.