March 20, 2003
Driving win32 GUIs with Python, part 2

Finding the target control

See Driving win32 GUIs with Python, part 1

So, we have our list of top level windows. You'll need to choose which one to burrow into. Now, the window's text may be all you need to make this decision. Often it is. But another useful criterion can be the window's class. Whether or not you need to know a window's class now, it will certainly come in handy later.

Windows supplies a GetClassName() API, but unfortunately Mark Hammond's win32 extensions don't wrap this. So, we'll use Thomas Heller's ctypes module, instead. I'm using version 0.4.

A couple of warnings here. Once you start using stuff like ctypes and the win32 extensions, you can crash Python, hard. I mean really, really, time to re-boot, hard. After all, you are basically poking the operating system with a stick, as my colleague Dan memorably put it. This will come as no surprise to C and C++ users, but Python usually protects you from this sort of thing. Secondly, I lifted the code for class name retrieval from c.l.py, and I basically have no idea how it works. ;-)

Anyway, here is how we get a window's class name:

import ctypes

def getClassName(hwnd):
resultString = ctypes.c_string("\000" * 32)
ctypes.windll.user32.GetClassNameA(hwnd, resultString, len(resultString))
return resultString.value

We'll alter our window enumeration callback thing to get use this:

def windowEnumerationHandler(hwnd, resultList):
'''Pass to win32gui.EnumWindows() to generate list of window handle, window text, window class tuples.'''
resultList.append((hwnd, win32gui.GetWindowText(hwnd), getClassName(hwnd)))

Now we'll get the class name in our list of windows, too.

This is the function that I use to find the top level window that I want. You may want something a little different - you may want an exact text match rather than a starts-with match, for example - but I'll leave the details to you.

def findTopWindow(wantedText=None, wantedClass=None):
topWindows = []
win32gui.EnumWindows(windowEnumerationHandler, topWindows)
for hwnd, windowText, windowClass in topWindows:
if wantedText and not windowText.startswith(wantedText):
continue
if wantedClass and not windowClass == wantedClass:
continue
return hwnd

OK, so, you can do something like findTopWindow(wantedText="ACE") to get the window handle of the required to window. Once you have this, you can burrow into its child windows using the win32gui.EnumChildWindows() function. This works pretty much the same as the win32gui.EnumWindows(), with an additional argument specifying which window's children to process. Also, for reasons obscure to me, it occasionally throws a win32gui.error exception. I think that this happens if the window is unable to have child windows (as opposed to just not actually having any, when you just get an empty list), but that's pretty much a guess really. Since I never get this exception for windows that have children that I'm interested in, it's not a problem.

The other complication is that the child windows can have children of their own, so we need to be able to to recurse through the hierarchy to find the window we want.

Here's my function for locating a control. For maximum flexibility, it takes a selection function, which should be able to spot when the required control has been found.

def findControl(topHwnd, selectionFunction):
def searchChildWindows(currentHwnd):
childWindows = []
try:
win32gui.EnumChildWindows(currentHwnd, windowEnumerationHandler, childWindows)
except win32gui.error, exception:
# This seems to mean that the control does *cannot* have child windows
return
for childHwnd, windowText, windowClass in childWindows:
# print "Found ", childHwnd, windowText, windowClass
if selectionFunction(childHwnd, windowText, windowClass):
return childHwnd
else:
descendentMatchingHwnd = searchChildWindows(childHwnd)
if descendentMatchingHwnd:
return descendentMatchingHwnd
return

return searchChildWindows(topHwnd)



And here we use it:

optDialog = findTopWindow(wantedText="Options")
print optDialog
def findAButtonCalledOK(hwnd, windowText, windowClass):
return windowClass == "Button" and windowText == "OK"
okButton = findControl(optDialog, findAButtonCalledOK)

There - so now we can find the control we want to do something with. Next, I'll actually do something to it...

Update: See the whole series: Driving win32 GUIs with Python, part 1, Driving win32 GUIs with Python, part 2, Driving win32 GUIs with Python, part 3, Driving win32 GUIs with Python, part 4 and 7 hours, one line of code.

Posted to Python by Simon Brunning at March 20, 2003 05:03 PM
Comments
Post a comment
Name:


Email Address:


URL:



Comments:


Remember info?