March 21, 2003
Driving win32 GUIs with Python, part 3

Some things you can do with a control once you have it.

See Driving win32 GUIs with Python, part 1 and Driving win32 GUIs with Python, part 2.

There are, of course, loads of things you can do with a control. Anything that you can do with a mouse or a keyboard can be automated. I'll just give a few simple examples, from which you should be able to extrapolate most things that you might need to do.

A great many of these actions are class specific - you can select one or many of a combo box's items, for example, but a button doesn't have items to select.

OK A nice simple one first. Let's click a button:

def click(hwnd):
win32gui.SendMessage(hwnd, win32con.WM_LBUTTONDOWN, 0, 0)
win32gui.SendMessage(hwnd, win32con.WM_LBUTTONUP, 0, 0)

We can use this function like so:

optDialog = findTopWindow(wantedText="Options")
def findAButtonCalledOK(hwnd, windowText, windowClass): # Define a function to find our button
return windowClass == "Button" and windowText == "OK"
okButton = findControl(optDialog, findAButtonCalledOK)
click(okButton)

You could do this more tersely with a lambda, by the way:

optDialog = findTopWindow(wantedText="Options")
okButton = findControl(optDialog, lambda hwnd, windowText, windowClass : windowClass == "Button" and windowText == "OK")
click(okButton)

I prefer the first form, but that's purely subjective.

And now, for my next trick, let's get the text from an Edit control. "But wait", you'll say, "can't you use the win32gui.GetWindowText() function"? No, actually, you can't. The thing is, this works for some classes of window - you can use win32gui.GetWindowText() to get the text of a button or a label, for example. But other classes don't have the kind of text that win32gui.GetWindowText() sets. An edit control is one of these - it has lines of text rather than just text. Here's a function which will get it for you:

def getEditText(hwnd):
result = []
bufferlength = struct.pack('i', 255)
linecount = win32gui.SendMessage(hwnd, win32con.EM_GETLINECOUNT, 0, 0)
for line in range(linecount):
linetext = bufferlength + "".ljust(253)
linelength = win32gui.SendMessage(hwnd, win32con.EM_GETLINE, line, linetext)
result.append(linetext[:linelength])
return result

The only complicated bit here is the struct stuff. When sending the win32con.EM_GETLINE, you need to pass a string, into which the line of text will be inserted. The first byte of this string must contain the total length of the string. struct to the rescue! It would be nice to wrap this up into a class, implementing a file-like interface, at some point. Or perhaps it should be a generator? Anyway, as it stands, you can use it like so:

genTop = findTopWindow(wantedText="Generating the Runtime")
def editFinder(hwnd, windowText, windowClass): return windowClass == "Edit"
genEdit = findControl(genTop, editFinder)
print getEditText(genEdit)

Now, combo-boxes. (These are the ones with a little down-arrow button to the right hand side, allowing you to select one (or perhaps more) from several options.) Here is the code to get a list of the values from a combo box:

def getComboboxItems(hwnd):
result = []
bufferlength = struct.pack('i', 255)
itemCount = win32gui.SendMessage(hwnd, win32con.CB_GETCOUNT, 0, 0)
for itemIndex in range(itemCount):
linetext = bufferlength + "".ljust(253)
linelength = win32gui.SendMessage(hwnd, win32con.CB_GETLBTEXT, itemIndex, linetext)
result.append(linetext[:linelength])
return result

Looks a bit like getEditText, doesn't it? I thought so - refactored version below:

def getMultipleWindowValues(hwnd, getCountMessage, getValueMessage):
result = []
bufferlength = struct.pack('i', 255)
count = win32gui.SendMessage(hwnd, getCountMessage, 0, 0)
for itemIndex in range(count):
value = bufferlength + "".ljust(253)
valueLength = win32gui.SendMessage(hwnd, getValueMessage, itemIndex, value)
result.append(value[:valueLength])
return result

def getComboboxItems(hwnd): return getMultipleWindowValues(hwnd, getCountMessage=win32con.CB_GETCOUNT, getValueMessage=win32con.CB_GETLBTEXT)
def getEditText(hwnd): return getMultipleWindowValues(hwnd, getCountMessage=win32con.EM_GETLINECOUNT, getValueMessage=win32con.EM_GETLINE)

Last thing today - selection a combo-box item.

def selectComboboxItem(hwnd, item):
win32gui.SendMessage(hwnd, win32con.CB_SETCURSEL, item, 0)
click(hwnd)
keystroke(hwnd, win32con.VK_RETURN)

def keystroke(hwnd, key):
win32gui.SendMessage(hwnd, win32con.WM_KEYDOWN, key, 0)
win32gui.SendMessage(hwnd, win32con.WM_KEYUP, key, 0)

I had hoped that the first of these messages would be enough. Unfortunately, it was not to be - while the selected item did change in the GUI, the application that I was automating didn't respond to the change without the click, and the drop-down list didn't disappear without the keystroke. If anyone can suggest a more elegant way of doing this, I'd be pleased to hear of it.

Here I put it all together

# Find the ACE window library selection combo
def findAceLibrarySelectionFunction(hwnd, windowText, windowClass): return windowClass == "ComboBox" and ("EVO1" in getComboboxItems(hwnd))
libCombo = findControl(findTopWindow(wantedText="ACE"), findAceLibrarySelectionFunction)

# Get a list of the combo items
items = getComboboxItems(libCombo)
print "Items: " + str(items)
import random

# Pick one at random, and select it
selectItemIndex = random.randrange(len(items))
print "Selecting: " + items[selectItemIndex]
selectComboboxItem(libCombo, selectItemIndex)

Well, that's just about it from me on this, really, though if anything else occurs to me, you'll be sure to know. ;-)

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 21, 2003 04:59 PM
Comments

Hi,
I am have having problems with getComboBoxItems() not returning the items in the list.

It looks like it is returning a pointer to the items.

Posted by: Rob M on March 21, 2005 02:49 PM

I use your program WinGuiAuto (great app!) along with my automation app PAMIE

I found that if you add the following line to
selectComboboxItem()

_sendNotifyMessage(hwnd, win32con.CBN_SELENDOK)

after

_sendNotifyMessage(hwnd, win32con.CBN_SELCHANGE)

in winGuiAuto.py

It will fire an event that may be associated with it such as "onchange"

Without this line it will just select the control
it will not process an events that may be associated with it. just and FYI

RLM
rmarchetti@vfa.com
calfdog@yahoo.com


Posted by: Rob Marchetti on March 21, 2005 02:55 PM

You might want to ask this in the WATSUP users list[1], Rob.

[1] http://lists.sourceforge.net/lists/listinfo/watsup-users

Posted by: Simon Brunning on March 21, 2005 02:57 PM

What about when you just want to get or set the text of the ComboBox?

def SetText(hwnd, text):
win32gui.SendMessage(hwnd, win32con.WM_SETTEXT, 0, text)

def GetText(hwnd):
buf_size = 1 + win32gui.SendMessage(hwnd, win32con.WM_GETTEXTLENGTH, 0, 0)
buffer = win32gui.PyMakeBuffer(buf_size)
win32gui.SendMessage(hwnd, win32con.WM_GETTEXT, buf_size, buffer)
return buffer[:buf_size]

Posted by: Omer Raviv on November 11, 2005 09:26 PM
Post a comment
Name:


Email Address:


URL:



Comments:


Remember info?