Doing windows


Let’s review polymorphism with Implements and see how it works in WinWatch. The IWindowsHelper interface is the template for any polymorphic class that needs to do something to a window (based on its handle). CWindowToFile is an IWindowsHelper class that writes window information to a file. CWindow­ToForm is an IWindowsHelper class that writes window information to a form—specifically to a label on the form and to a treeview control.


Note that IterateChildWindows and IWindowsHelper are general elements that you might use in any project. They’re in the VBCore project. CWindowToFile and CWindowToForm are project specific. They are designed for WinWatch, which is the only project that is likely to need them. Polymorphism turns object-oriented programming upside down. When you write classes for encapsulation, you usually create one class to be used from many programs. When you write classes for polymorphism, you have only one interface, but you create many project-specific classes that implement the interface. You can see how this works in the WinWatch code that uses the IWindowsHelper classes:

Private Sub RefreshFullWinList(fLogFile As Boolean)
Const sLog = "WINLIST.TXT"

Call LockWindowUpdate(tvwWin.hWnd)
tvwWin.Nodes.Clear
HourGlass Me
If fLogFile Then
lblMsg.Caption = "Creating log file " & sLog & "..."
nFileCur = FreeFile
Open sLog For Output As nFileCur
Print #nFileCur, sEmpty
Print #nFileCur, "Window List " & sCrLf
Dim helperFile As CWindowToFile
Set helperFile = New CWindowToFile
helperFile.FileNumber = nFileCur
''@B IterateChildWindows
Call IterateChildWindows(-1, GetDesktopWindow(), helperFile)
''@E IterateChildWindows
Close nFileCur
Else
lblMsg.Caption = "Building window list..."
Dim helperForm As CWindowToForm
Set helperForm = New CWindowToForm
Set helperForm.TreeViewControl = tvwWin
helperForm.ShowInvisible = chkInvisible
Call IterateChildWindows(-1, GetDesktopWindow(), helperForm)
End If
lblMsg.Caption = sMsg
HourGlass Me
tvwWin.Refresh
Call LockWindowUpdate(hNull)
End Sub

You create and pass to IterateChildWindows a completely different object, depending on what you’re doing. The ISortHelper interface and the CSortHelper class from Chapter 5 at least had object-oriented pretensions. But the IWindows­Helper interface and the CWindowToFile and CWindowToForm classes have no purpose other than to provide a platform for the DoWindow method. In fact, DoWindow is almost all there is to these classes.


Here’s the interface the classes are built on:

Function DoWindow(ByVal iLevel As Integer, _
ByVal hWnd As Long) As Long
End Function

That’s all there is to it. Here’s the CWindowToFile version:

Implements IWindowsHelper

Public FileNumber As Integer

Private Function IWindowsHelper_DoWindow(ByVal iLevel As Integer, _
ByVal hWnd As Long) As Long

BugAssert hWnd <> hNull
‘ Ignore desktop window (level -1)
If iLevel >= 0 Then
‘ Write data to log file
Print #FileNumber, GetWndInfo(hWnd, iLevel)
End If
‘ Always successful
IWindowsHelper_DoWindow = hNull
End Function

Recursion


A good recursive algorithm looks simple and symmetrical but has a magical complexity when you watch it in action. Code your recursive routines carefully to avoid recursing forever. Recursion works best if you have a limited task with clean exits. Also be sure that you don’t recurse too deeply and eat up all your stack space. IterateChildWindows recurses once for each level of child windows, but you’re not likely to find more than five levels of child windows. In other words, the algorithm recurses frequently but not deeply. Each level releases its stack resources before going on to the next.


If you use algorithms that recurse deeply (recursive sorting algorithms, for example), try to minimize your stack use. Don’t use more parameters than necessary, and declare local variables with Static rather than Dim. Static variables save stack space because they aren’t stored on the stack. You’ll need to watch out for side effects, however. Static variables retain their values across calls, so you should avoid using Static for any variable that you don’t specifically initialize on each entrance.


You must also pay attention to how you declare parameters. Notice that the iLevel and hWnd parameters of IterateChildWindows are passed by value (using ByVal) rather than by reference. Each level keeps its own local arguments on the stack so that changes to the current level don’t affect other levels. If you passed by reference, you’d be affecting the hWnd of the higher-level calls each time you set hWnd in a recursive call. Everything would be fine as long as you were recursing down, but you’d be lost as soon as you started back up.


This class does have one independent property, FileNumber, which must be set by the caller. But the only code is the implementation of DoWindow. It calls the GetWndInfo function to analyze and describe the window. The CWindowTo­Form version is pretty much the same except that it updates a treeview control instead of writing to a file.


Both IterateChildWindows and the DoWindow method are functions. This enables you to search for a window with certain features. For example, you could write your own version of FindWindow to search the window hierarchy for a window with a given class, title, or other feature. Your DoWindow function would return the window handle when it found the appropriate window, but hNull in all other cases. Any found handle would bubble up through the levels of recursion, stopping the iteration process and returning the handle via the top-level call to IterateChildWindows. In the WinWatch versions of DoWindow, we want to continue as long as any windows are left, so we always return hNull to indicate that DoWindow hasn’t found the magic window. In WinWatch, the outer call to IterateChildWindows just throws away the returned handle, using the Call syntax.


You can use the log file created by WinWatch to get a snapshot of the window state. Want to know the class name of a button or an icon in a particular program? Run WinWatch and click the Log File button.