The top window list


The Top Windows list box in WinWatch lists each top-level window. Top-level windows are what you probably think of as Windows programs. In fact, the top-level window list in WinWatch usually will show the same programs as the taskbar in Windows NT 4 and Windows 95. My bet is that Windows refreshes the taskbar with code very similar to this:

Private Sub RefreshTopWinList()
Dim sTitle As String, hWnd As Long

SetRedraw lstTopWin, False
lstTopWin.Clear
' Get first top-level window
hWnd = GetWindow(GetDesktopWindow(), GW_CHILD)
BugAssert hWnd <> hNull
' Iterate through remaining windows
Do While hWnd <> hNull
sTitle = WindowTextLineFromWnd(hWnd)
' Determine whether to display titled, visible, and unowned
If IsVisibleTopWnd(hWnd, chkBlank, _
chkInvisible, chkOwned) Then
lstTopWin.AddItem sTitle
lstTopWin.ItemData(lstTopWin.NewIndex) = hWnd
End If
' Get next child
hWnd = GetWindow(hWnd, GW_HWNDNEXT)
Loop
SetRedraw lstTopWin, True
End Sub

The only thing the taskbar has that this code doesn’t provide is small icons, and you’ll learn how to add them in Chapter 8.


This iteration loop works a lot like the loops for Process32First and Module32­First, except that you pass the constants GW_HWNDCHILD and GW_HWNDNEXT to GetWindow. You could also say that it’s like IterateChildWindows (from
“Iterating the hard way” earlier in this chapter) without the recursion. You could do the same thing with EnumWindows and your own EnumWindowsProc function, but in this case I think GetWindow is simpler. Either way, GetWindow and EnumWindows are standard Win32 functions, so there’s no need to jump through portability hoops as we did with processes and modules.


For each found window, you check to ensure that the window meets certain criteria—that it has a title, is visible, and is not owned by another window. Windows that meet all the criteria (as determined by IsVisibleTopWnd) have their titles and window handles placed in the list box. Here’s how the decision is made:

Function IsVisibleTopWnd(hWnd As Long, _
Optional IgnoreEmpty As Boolean = False, _
Optional IgnoreVisible As Boolean = False, _
Optional IgnoreOwned As Boolean = False) _
As Boolean
If IgnoreEmpty Or WindowTextFromWnd(hWnd) <> sEmpty Then
If IgnoreVisible Or IsWindowVisible(hWnd) Then
If IgnoreOwned Or GetWindow(hWnd, GW_OWNER) = hNull Then
IsVisibleTopWnd = True
End If
End If
End If
End Function

The code to fill the top window list uses the values of the Show check boxes to determine which windows to display:

If IsVisibleTopWnd(hWnd, chkBlank, _
chkInvisible, chkOwned) Then
lstTopWin.AddItem sTitle
lstTopWin.ItemData(lstTopWin.NewIndex) = hWnd
End If

If you experiment by checking the boxes, one at a time, you’ll learn some interesting facts about Windows. For example, if you check the Show Invisible box, you’ll learn that Windows maintains a lot of invisible windows behind the scenes. There are also a bunch of windows with blank titles. If you check the Show Owned box, you’ll see lots of modeless dialog boxes. For example, if Visual Basic is running, you’ll see that all your code windows are actually top-level windows owned by the Visual Basic window.


You might also want to check out RefreshAllLists. This procedure calls Refresh­TopWinList, RefreshProcessList, and RefreshFullWinList (more on that later) to update all the window information on the display. It is called when you load WinWatch, when you click the Refresh button, when you change any of the Show check boxes, or when you select a window that no longer exists.


Now that we’ve covered top-level windows, modules, and processes, we are ready to talk a little more about handles for windows, modules, and processes.