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}
".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:WorkDevStackOverflowq053778227>"e:WorkDevVEnvspy_064_03.06.08_test0Scriptspython.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)
- 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.