5

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

CristiFati
  • 38,250
  • 9
  • 50
  • 87
hetijav
  • 159
  • 2
  • 12

1 Answers1

5

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.

CristiFati
  • 38,250
  • 9
  • 50
  • 87
  • `Python 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:30:26) [MSC v.1500 64 bit (AMD64)] on win32 Handling 0x01DB1EE2 - [] Traceback (most recent call last): File "Stackoverflow.py", line 37, in main() File "Stackoverflow.py", line 31, in main handle_window(np_wnd, child_index=0) File "Stackoverflow.py", line 24, in handle_window raise e pywintypes.error: (1400, 'EnumChildWindows', 'Invalid window handle.')` I use python 2.7 btw – hetijav Jan 29 '19 at 16:39
  • Of course. I hardcoded the existing windows handles as well (2 lines with comments in *main*). You'll have to provide the handles on your system. – CristiFati Jan 29 '19 at 16:43
  • How do I get the handle of device manager? I only know how to get the hwnd of a process. – hetijav Jan 29 '19 at 17:18
  • The same way you did it for `hwndMain = 133082`. You can see the processes for *Notepad* and *Device Manager* in *Task Manager*. Or as I specified, you can use a tool (e.g. *Spy++*) if you have one installed, to get the window handle for you. – CristiFati Jan 29 '19 at 17:22
  • Oh, I didnt knew you could do that. I used a python scipts to get the hwnd of processes. Can you tell me how to see that in task manager. – hetijav Jan 29 '19 at 17:26
  • You can use the same scripts to get the handles for the processes again, I don't see the problem. Come on!, *Google*ing e.g. "see pid in task manager" would yield many hits with images and everything, waay better than I could explain in a comment. – CristiFati Jan 29 '19 at 17:31
  • I know how to get a PID, but `11964` doesnt really look like `0x000E2042` – hetijav Jan 29 '19 at 17:41
  • They don't have to be. One is a pid, one is a window handle. One is in decimal, the other is hex. When I wrote that code I followed the example in yours. Things are quite simple *np\_wnd* must be initialized to *Notepad* main window handle, and *dm\_wnd* to *Device Manager*. `11964 == 0x00002EBC` – CristiFati Jan 29 '19 at 17:45
  • Thanks, but what do I change if I want to send an other key? Do I change `win32con.WM_KEYDOWN` to for example `win32con.WM_KEYUP`? – hetijav Jan 30 '19 at 11:26
  • :) yes. But bear in mind that *WM\_KEYDOWN* is an **event** (user pressed down a key). The key itself is *VK\_DOWN*. If you want to send a regular key, e.g. ***A***, the code would be `win32gui.SendMessage(wnd, win32con.WM_CHAR, 0x41, 0)`. – CristiFati Jan 30 '19 at 11:43