Iterating the hard way
The hard way of iterating Windows is to use the GetWindow API function. My IterateChildWindows function will recursively traverse the window tree, doing some project-specific task for each window. In other words, IterateChildWindows will do out in the open what the EnumChildWindows function does behind the scenes. EnumChildWindows takes a procedure variable argument to provide the project-specific task for each window, but IterateChildWindows is written in a wimpy language that doesn’t support procedure variables. Instead, we’ll have to do the same object hack we did for sorting (see “Sorting, Shuffling, and Searching” in Chapter 5). But let’s look at the iterator before we worry about the iterees.
One caveat: the Windows API documentation warns that a window loop like this one risks getting caught in an infinite loop or accessing the handle of a window that no longer exists. EnumChildWindows, on the other hand, is guaranteed to be reliable. I haven’t had any problems with IterateChildWindows, but I’m using it as an exploration tool rather than as a way to add functionality to real programs. Use it only when you know that the window hierarchy will not change while it’s working.
Recursion makes IterateChildWindows deceptively short, but it’s not as simple as it looks:
Function IterateChildWindows(ByVal iLevel As Integer, _
ByVal hWnd As Long, _
helper As IWindowsHelper) As Long
BugAssert hWnd <> hNull
' Handle current window, allowing user to fail
IterateChildWindows = helper.DoWindow(iLevel, hWnd)
If IterateChildWindows <> hNull Then Exit Function
' Get its child (if any)
hWnd = GetWindow(hWnd, GW_CHILD)
' Iterate through each child window
Do While hWnd <> hNull
IterateChildWindows = _
IterateChildWindows(iLevel + 1, hWnd, helper)
If IterateChildWindows <> hNull Then Exit Function
' Get next child
hWnd = GetWindow(hWnd, GW_HWNDNEXT)
' Give other processes some cycles
DoEvents
Loop
' Nothing found
IterateChildWindows = hNull
End Function
IterateChildWindows is located in WINITER.BAS. The function has three parameters. The first indicates the window level. It will be incremented each time you recurse to a new level. You can use it to output tabs or indicate indent levels. (The iLevel parameter gives IterateChildWindows an advantage over the system EnumChildWindows, which offers no information about the level.) The second parameter is the window handle of the starting window. You must seed each of these values for the first call to IterateChildWindows, but then it automatically figures out the appropriate arguments for subsequent calls to itself. If you want to start at the desktop, use the GetDesktopWindow API function (as WinWatch does). The last parameter is the helper object that we’ll discuss in the next section. A call looks like this:
Call IterateChildWindows(-1, GetDesktopWindow(), helperFile)
WinWatch passes -1 as the starting level to prevent the DoWindow method from putting the desktop window in the treeview control. You could start with some other window handle, such as the handle of the current program:
Call IterateChildWindows(0, Me.hWnd, helperFile)
Once you have the initial window handle, you can get the handle of its first child window by calling GetWindow with GW_CHILD. The function returns the child window handle, or hNull if the window has no child. You get the next window at the same level (the next sibling) by calling GetWindow with GW_HWNDNEXT. The IterateChildWindows function first gets the child of the current window and then loops through all the sibling windows, calling itself to get the children of each sibling. If that sounds confusing, try stepping through the function. You’ll soon feel disoriented, if not completely lost. Computers do recursion better than humans.
Just as you can write IterateChildWindows to emulate the EnumChildWindows API function, you can write IterateWindows to emulate the EnumWindows API function. EnumWindows simply processes top-level windows. (See the RefreshTopWinList procedure in WINWATCH.FRM.) Realistically, you’d seldom use IterateWindows. EnumWindows is easier and just as powerful.