Automate edit control filling in Windows GUI application with Python and Spy++

There is a Windows GUI application in which you need to fill text to edit controls. There are four pages, each page has two edit controls. Each time you need to fill text to two edit controls on a page then click next button to next page and so on. If you need to use the application daily and have to fill almost the same text on the same controls, things can quickly becomes boring and tedious, it's not fun to do the same thing over and over again.

Python and Spy++ come to the rescue, with the two you can almost simulate and automate any GUI application operation sequences. A macro recorder will be able to do the same thing, but a Python script gives you more control over how things get done, it's more accurate and flexible.

Here is the script can fill win32 edit controls automatically.

from ctypes import *
import win32api
import win32con
import win32gui
import time
EnumChildWindows = windll.user32.EnumChildWindows
EnumChildProc = WINFUNCTYPE(c_bool, c_int, POINTER(c_int))
GetWindowText = windll.user32.GetWindowTextW
GetWindowTextLength = windll.user32.GetWindowTextLengthW
IsWindowVisible = windll.user32.IsWindowVisible
GetClassName = windll.user32.GetClassNameW
titles = []
def foreach_window(hwnd, lParam):
    if IsWindowVisible(hwnd):
        length = GetWindowTextLength(hwnd)
        classname = create_unicode_buffer(100 + 1)
        GetClassName(hwnd, classname, 100 + 1)
        buff = create_unicode_buffer(length + 1)
        GetWindowText(hwnd, buff, length + 1)
        titles.append((hex(hwnd), buff.value, classname.value, windll.user32.IsIconic(hwnd)))
    return True
def clickat(hwnd,x,y):
    pos = win32api.MAKELONG(x, y)
    win32gui.SendMessage(hwnd, win32con.WM_ACTIVATE, win32con.WA_ACTIVE, 0)
    win32api.SendMessage(hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, pos) 
    win32api.SendMessage(hwnd, win32con.WM_LBUTTONUP, win32con.MK_LBUTTON, pos)
def nextpage():
def gethwnd():
    global titles
    titles = []
    EnumChildWindows(0x00020218, EnumChildProc(foreach_window), 0)
    return int(titles[1][0],16)
def settext(hwnd,title):
    windll.user32.SendMessageW(hwnd, win32con.WM_SETTEXT, 0,title)    
def fillpageno(no):
    clickat(0x00020218,121,23) #click edit control
    edithwnd = gethwnd()
    clickat(0x00020218,121,23) #click again to commit
def fillpagename(name):
    clickat(0x00020218,121,43) #click edit control
    edithwnd = gethwnd() #without the sleep, this may fail to get hwnd
    clickat(0x00020218,121,43) #click again to commit
def autofill(n2,n3,n4):

There are a few things to notice here. In this example, the parent window of edit controls always have the same HWND, we can find the HWND by using Spy++, it's quite a useful tool to locate windows and their HWNDs. It's a little bit tricky to catch the HWNDs of edit controls inside an AfxWnd42 window because they are dynamically created and destroyed as the focus entering or leaving the edit controls. From my observation, you have to first click on a edit control and then call EnumChildWindows on the parent window to find out the HWND of the newly created control, if you click another control and then go back to click the previous again, the HWND retrieved by EnumChildWindows is different. There is no stable HWNDs for these edit controls, they must be retrieved dynamically everytime the edit control has the focus.

To make a click on anything, you need the HWND of the thing and mouse position. To get these data, simply open message monitoring interface on a window in Spy++ and do a click on the window or control, the recorded WM_PARENTNOTIFY or WM_LBUTTONDOWN will tell your the position.

Change the text of edit control involves three steps, first to click on the control to get the focus, then send WM_SETTEXT to set the content of the control, and for some controls, you need to click again to commit the changes, otherwise the content will be reverted when the focus leaves the control.

The last one is there should be a pause between two sequential change operations on different edit controls. Without the pause, the EnumChildWindows may fail to retrieve the edit control. The possible reason my be that for manual operation, the delay between clicking and window creation is unnoticeable, but in a Python script, things happens too fast, it's possible that the enumeration is called before the child window is available. Add a pause will slow the script down.