键盘事件未使用 pywin32 发送到窗口 [英] Keyboard event not sent to window with pywin32

查看:43
本文介绍了键盘事件未使用 pywin32 发送到窗口的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我编写了一个代码,可以从我想要的任何程序中获取 HWND.如果你问的话,这就是我得到 hwnd 的方式.

以下代码应调出设备管理器并将向下箭头发送给程序.

但确实如此.它确实会调出设备管理器,但不会向程序发送向下箭头键,至少没有任何反应.

如果我使用记事本窗口的 hwnd 代码更改 hwndMain 编号,该代码会起作用并发送向下箭头键

导入win32api导入 win32con导入 win32gui导入时间hwndMain = 133082hwndChild = win32gui.GetWindow(hwndMain, win32con.GW_CHILD)win32gui.SetForegroundWindow(hwndMain)时间.睡眠(1)win32api.SendMessage(hwndChild, win32con.WM_CHAR, 0x28, 0)

编辑

我试过了

win32api.SendMessage(hwndChild, win32con.WM_CHAR, win32con.WM_KEYDOWN, 0)

代替

win32api.SendMessage(hwndChild, win32con.WM_CHAR, 0x28, 0)

但这也行不通.

我使用的是 python 2.7

解决方案

每个Win窗口可以有0个或更多个子窗口,并且这些子窗口中的每一个也可以有 0 个或更多自己的子窗口,依此类推...所以每个窗口可能有一个完整的子树.

关于窗户的内容远不止眼前一亮.用户可能看着一个(顶部)窗口并想象它的树以某种方式看起来,但实际上这棵树看起来可能完全不同(更复杂),因为可能有一些窗口不可见.

当将消息发送到窗口并期望发生某种行为时,消息必须发送到确切的窗口(或其设计为转发它),否则该消息将被简单地忽略(因为错误 窗口不处理这种消息).
在我们的例子中,这意味着WM_KEYDOWN(或WM_CHAR)消息应该发送到:

  • 保存记事本
  • 文本的(编辑)窗口
  • 保存设备管理器的设备列表的 (TreeView) 窗口

您正在使用 [ActiveState.Docs]:win32gui.GetWindow,它包装了 [MS.Docs]:GetWindow 函数 声明(对于GW_CHILD):

<块引用>

检索到的句柄标识Z序顶部的子窗口,如果指定的窗口是父窗口;否则,检索到的句柄为NULL.该函数仅检查指定窗口的子窗口.它不检查后代窗口.

巧合,对于记事本将消息发送给它的第一个st子节点有效,因为那个子节点变成了Edit 我上面提到的窗口(除了那个孩子,记事本只有另一个是StatusBar,就是这样,这些窗口都没有任何孩子他们自己的).

另一方面,对于设备管理器来说,事情就没有那么简单了.如您所见,它的结构更加复杂(例如,ToolBar 窗口是可见的).按照建议,为了使用 Windows,我使用 [MS.Docs]:EnumChildWindows 函数.

code.py:

#!/usr/bin/env python3导入系统导入pywintypes导入 win32gui导入 win32condef enum_child_proc(wnd, param):打印(处理孩子 0x{:08X} - [{:}] - 0x{:08X}".format(wnd, win32gui.GetWindowText(wnd), win32gui.GetParent(wnd)))如果参数[0] >= 0:如果参数[1] == 参数[0]:win32gui.SendMessage(wnd, win32con.WM_KEYDOWN, win32con.VK_DOWN, 0)返回 0参数[1] += 1def handle_window(wnd, child_index=-1):print("处理 0x{:08X} - [{:}]".format(wnd, win32gui.GetWindowText(wnd)))cur_child = 0参数 = [child_index, cur_child]尝试:win32gui.EnumChildWindows(wnd, enum_child_proc, param)除了 pywintypes.error 为 e:如果 child_index <0 或 e.args[0]:提高e定义主():np_wnd = 0x01DB1EE2 # 记事本句柄dm_wnd = 0x000E2042 # 设备管理器句柄句柄窗口(np_wnd,child_index=0)句柄窗口(dm_wnd,child_index=6)如果 __name__ == "__main__":print("Python {:s} on {:s}\n".format(sys.version, sys.platform))主要的()

注意事项:

  • 我硬编码了 2 个窗口句柄(np_wnddm_wnd).显然,它们将无效(因为我关闭了窗户,它们在我的机器上也不再有效),并且它们的值需要更改
  • 为了找到窗口的句柄(及其一些子窗口),我使用了 Spy++ ([MS.Docs]: How to: Start Spy++),它是 VStudio 的一部分,但我相信还有很多其他类似的应用程序

输出:

<块引用>

e:\Work\Dev\StackOverflow\q053778227>"e:\Work\Dev\VEnvs\py_064_03.06.08_test0\Scripts\python.exe"代码.pyPython 3.6.8 (tags/v3.6.8:3c6b436a57, Dec 24 2018, 00:16:47) [MSC v.1916 64 位 (AMD64)] on win32处理 0x01DB1EE2 - [无标题 - 记事本]处理子 0x01811FA4 - [] - 0x01DB1EE2处理 0x000E2042 - [设备管理器]处理子 0x00621A5A - [] - 0x000E2042处理子进程 0x01991F44 - [设备管理器] - 0x00621A5A处理子 0x01691F3E - [] - 0x01991F44处理子 0x000C20B0 - [] - 0x01691F3E处理子 0x004D2000 - [] - 0x000C20B0处理子进程 0x004420CA - [] - 0x004D2000处理子 0x01191F20 - [] - 0x004420CA

从输出中可以看出,TreeView 窗口是 7th 个子窗口(第 7 个设备管理器窗口的子 :) ) 意味着 它们之间有 6 个中间(和不可见)窗口(忽略该消息).

尽管代码对所讨论的窗口起到了作用,目前没有适用于任何窗口的方法(或者如果有,我不知道).我必须提到,我已经尝试通过查看树来确定树中感兴趣的子窗口:

  • 姓名
  • 班级
  • 风格(MS 文档在这方面很差)
    • 扩展样式
  • 位置(相对于其母公司)
  • SendMessage 的返回码

但我找不到任何可以将它与其他窗口区分开来的东西.我唯一注意到的是,对于记事本,所需的窗口是枚举的第一个st子窗口,而对于设备管理器,它是第 7st 一个,所以我根据这个事实(child_index)做了过滤,但我认为它完全不可靠.

作为替代方案,可以根本不进行过滤,并将消息发送到树中的所有子窗口,但这可能会产生不良影响,因为可能有其他窗口响应该消息.例如设备管理器树由~30个子窗口组成.

最后,我还要提一下,有些窗口(像Chrome这样的网络浏览器)有自己的windows系统,所以这些都行不通.

I've wrote A code that gets the HWND from any program I want. So thats how I got the hwnd if your asking.

The following code should bring up device manger and send the down arrow to the program.

But it doenst. It does bring up the device manager but it doesnt send the arrow down key to the program, at least nothing happens.

If I change the hwndMain number with the hwnd code of a notepad window, the code does work and sends the arrow down key

import win32api
import win32con
import win32gui
import time

hwndMain = 133082
hwndChild = win32gui.GetWindow(hwndMain, win32con.GW_CHILD)
win32gui.SetForegroundWindow(hwndMain)
time.sleep(1)

win32api.SendMessage(hwndChild, win32con.WM_CHAR, 0x28, 0)

EDIT

I've tried

win32api.SendMessage(hwndChild, win32con.WM_CHAR, win32con.WM_KEYDOWN, 0)

Instead of

win32api.SendMessage(hwndChild, win32con.WM_CHAR, 0x28, 0)

But that doesnt work either.

I'm on python 2.7

解决方案

Every Win window can have 0 or more child windows, and each of those child windows can also have 0 or more children of their own, and so on... So each window may have a whole tree of children.

There is more about windows, than meets the eye. The user might look at one (top) window and imagine that its tree looks in a certain way, when in fact that tree could look totally different (more complex), as there might be some windows that are not visible.

When sending the message to a window and expecting a certain behavior to occur, the message must be sent to the exact window (or to one of its ancestors which are designed in such a way to forward it), otherwise the message will simply be ignored (as the wrong window doesn't handle that kind of message).
In our case, it means that the WM_KEYDOWN (or WM_CHAR) message should be sent to:

  • The (Edit) window that holds the text for Notepad
  • The (TreeView) window that holds the device list for Device Manager

You are using [ActiveState.Docs]: win32gui.GetWindow, which wraps [MS.Docs]: GetWindow function which states (for GW_CHILD):

The retrieved handle identifies the child window at the top of the Z order, if the specified window is a parent window; otherwise, the retrieved handle is NULL. The function examines only child windows of the specified window. It does not examine descendant windows.

Coincidentally, for Notepad sending the message to its 1st child works, because that child turned to be the very Edit window that I mentioned above (besides that child, Notepad only has another one which is the StatusBar, and that's it, none of these windows has any children of their own).

For Device Manager on the other hand things are not so simple. As you can see, its structure is more complex (e.g. ToolBar window is visible). As recommended, In order to work with windows, I'm using [MS.Docs]: EnumChildWindows function.

code.py:

#!/usr/bin/env python3

import sys
import pywintypes
import win32gui
import win32con


def enum_child_proc(wnd, param):
    print("    Handling child 0x{:08X} - [{:}] - 0x{:08X}".format(wnd, win32gui.GetWindowText(wnd), win32gui.GetParent(wnd)))
    if param[0] >= 0:
        if param[1] == param[0]:
            win32gui.SendMessage(wnd, win32con.WM_KEYDOWN, win32con.VK_DOWN, 0)
            return 0
        param[1] += 1


def handle_window(wnd, child_index=-1):
    print("Handling 0x{:08X} - [{:}]".format(wnd, win32gui.GetWindowText(wnd)))
    cur_child = 0
    param = [child_index, cur_child]
    try:
        win32gui.EnumChildWindows(wnd, enum_child_proc, param)
    except pywintypes.error as e:
        if child_index < 0 or e.args[0]:
            raise e


def main():
    np_wnd = 0x01DB1EE2  # Notepad handle
    dm_wnd = 0x000E2042  # Device Manager handle

    handle_window(np_wnd, child_index=0)
    handle_window(dm_wnd, child_index=6)


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

Notes:

  • I hardcoded the 2 window handles (np_wnd, dm_wnd). Obviously, they won't be valid (they are no longer valid on my machine either since I closed the windows), and their values need to be changed
  • In order to find a window's handle (and some of its children) I'm using Spy++ ([MS.Docs]: How to: Start Spy++), which is part of VStudio, but I'm sure there are tons of other similar applications

Output:

e:\Work\Dev\StackOverflow\q053778227>"e:\Work\Dev\VEnvs\py_064_03.06.08_test0\Scripts\python.exe" code.py
Python 3.6.8 (tags/v3.6.8:3c6b436a57, Dec 24 2018, 00:16:47) [MSC v.1916 64 bit (AMD64)] on win32

Handling 0x01DB1EE2 - [Untitled - Notepad]
    Handling child 0x01811FA4 - [] - 0x01DB1EE2
Handling 0x000E2042 - [Device Manager]
    Handling child 0x00621A5A - [] - 0x000E2042
    Handling child 0x01991F44 - [Device Manager] - 0x00621A5A
    Handling child 0x01691F3E - [] - 0x01991F44
    Handling child 0x000C20B0 - [] - 0x01691F3E
    Handling child 0x004D2000 - [] - 0x000C20B0
    Handling child 0x004420CA - [] - 0x004D2000
    Handling child 0x01191F20 - [] - 0x004420CA

As seen from the output, the TreeView window is the 7th child (of the 7th child :) ) of the Device Manager window meaning that there are 6 intermediary (and invisible) windows between them (which ignore that message).

Although the code did the trick for the windows in question, there's no current recipe that works for any window (or if there is, I am not aware of it). I must mention that I've tried to determine the child window of interest in the tree by looking at its:

  • Name
  • Class
  • Style (MS doc is quite poor in this area)
    • Extended style
  • Position (in relation to its parent)
  • SendMessage's return code

but I couldn't find anything that would differentiate it from other windows. The only thing that I noticed is that for Notepad, the desired window is the 1st child enumerated, while for Device Manager it's the 7st one, so I did the filtering based on this fact (child_index), but I consider it totally unreliable.

As an alternative, there could be no filtering at all, and the message sent to all the child windows in the tree, but this might have unwanted effects, as there could be other windows that respond to that message. For example Device Manager tree consists of ~30 child windows.

At the end, I would also like to mention that some windows (web browsers like Chrome), have their own windows systems, so none of this will work.

这篇关于键盘事件未使用 pywin32 发送到窗口的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆