FindWindowEx v EnumChildWindows

Mathimagics
12-23-2003, 12:04 AM
.
Child windows - Enumerate or Find?

A common requirement is to be able to enumerate (list all) or find a child window that belongs to some target parent window, or to some descendant of that parent window.

There are 2 powerful API tools for this job, FindWindowEx and EnumChildWindows. This tutorial assumes you are familiar with how EnumChildWindows works, but even if you aren't you can still use the code.

I'll deal with the 2 functions in the following posts. All examples will use the same module declarations shown below.

But first a couple of important API issues relevant to the topic:

GetWindowText vs WM_GETTEXT

The GetWindowText function does NOT work for child windows in another process, only top-level windows.

To get the text of any window that has a text property, you should use the SendMessage function with the WM_GETTEXT message.

In these examples I declare an alias for SendMessage, called SendMessageS in which lParam is declared as a String. This is convenient when using the WM_GETTEXT message in VB.


FindWindowEx vs Caption/Classname params

Many standard API declarations for FindWindow and FindWindowEx specify the final 2 parameters as type String. This is because these functions can be used to search for particular captions and/or classnames.

For enumeration or iterative finding, we want to pass zero for both these parameters - not the string "0", nor an empty string, but 0.
To do this, we need to declare FindWindowEx as having both parameters specified as "ByVal param As Long".

In these examples I declare an alias for FindWindowEx, called FindWindowX.

Option Explicit
'
' Enumerating and finding child windows
' using FindWindowEx and EnumChildWindows
'
' MathImagics@yahoo.co.uk
'
'====================================================================
'
' note: alias API declarations FindWindowEx and SendmessageS
'
'====================================================================
Declare Function GetParent Lib "user32" (ByVal hwnd As Long) As Long
Declare Function EnumChildWindows Lib "user32" (ByVal hWndParent _
As Long, ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, _
ByVal lpClassName As String, ByVal nMaxCount As Long) As Long

Declare Function SendMessageS Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, _
ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long
Declare Function FindWindowX Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Long, _
ByVal hWnd2 As Long, ByVal lpsz1 As Long, ByVal lpsz2 As Long) As Long

Public Const WM_GETTEXT = &HD

Mathimagics
12-23-2003, 12:35 AM
EnumChildWindows

EnumChildWindows will pass through every child window of the starting window, calling the enumertion function whose address you specify.

The enumeration function is passed a new window handle each time your function is called.

Also, if any window handle it passes you itself has children, it will call you for each of those as well, enumerating in this way the entire window subtree.

Here is a simple enumeration function called EnumChildWindow, which prints the window's handle, classname, and text (if available) in the debug window.

Note that the function MUST be placed in a Module, not on a Form! You can call from anywhere, of course.

Function EnumChildWindow(ByVal hChild As Long, ByVal lParam As Long) As Long

Dim wClass As String, wText As String
Dim j As Integer

wClass = Space(64)
j = GetClassName(hChild, wClass, 63)
wClass = Left(wClass, j)

wText = Space(256)
j = SendMessageS(hChild, WM_GETTEXT, 255, wText)
wText = Left(wText, j)

Debug.Print "Enum " & hChild; ", "; wClass;
If Len(wText) Then Debug.Print ", """; wText; """";
Debug.Print

EnumChildWindow = 1 ' Continue enumeration
End Function

The parameter lParam is not used by us, but must be declared in the function for compatibility with the Win32 API.

It can be useful if you want to have the same enumeration function perform different tasks (a generic enumerator).

When you call EnumChildWindows, you can give a different value that will then be passed to your enumeration function, and this value can be used as a "mode" selector during the current enumeration.

To list a window and all it's children, we call our enumeration function once directly to "enumerate" the starting window, then call EnumChildWindows to enumerate the children:
EnumChildWindow hParent, 0 ' one call directly to list parent
EnumChildWindows hParent, AddressOf EnumChildWindow, 0

Here's some sample output from a Windows Explorer application window:
Enum 6423866, ExploreWClass, "My Documents"
Enum 3671040, WorkerW
Enum 9372726, ReBarWindow32
Enum 2557106, ToolbarWindow32
Enum 984332, ComboBoxEx32, "My Documents"
Enum 1705154, ToolbarWindow32
Enum 2557142, ComboBox
Enum 1377446, Edit, "My Documents"
Enum 7931064, ToolbarWindow32
Enum 2622682, WorkerW
Enum 11470104, ToolbarWindow32
Enum 3212492, WorkerW
Enum 1901600, msctls_statusbar32, "50 object(s) (Disk free space: 14.9 GB)"
Enum 2294932, SHELLDLL_DefView
Enum 3081442, SysListView32
Enum 2294824, SysHeader32
Enum 2426026, BaseBar
Enum 1639554, ReBarWindow32
Enum 1049860, ToolbarWindow32
Enum 2819166, SysTreeView32, "Folders"

Mathimagics
12-23-2003, 01:46 AM
FindWindowEx

FindWindowEx returns a single window handle. When used to enumerate child windows, one call is made to find the first child of a window, then further calls are made to get the next sibling, until it returns 0.

It's just like the VB Dir$ function, or the TreeView .Child (first child) and .Next properties!

Here's how we'd enumerate all children of a window (we'll use the EnumChildWindow function already listed to report the window details)

Dim hChild As Long

EnumChildWindow hParent, 0

hChild = FindWindowX(hParent, 0, 0, 0) ' get first child of hParent
While hChild
EnumChildWindow hChild, 0 ' call our EnumProc directly
hChild = FindWindowX(hParent, hChild, 0, 0) ' get next sibling of hChild
Wend
Here's the results from the same Windows Explorer application window used in the previous example.:

[list] Enum 6423866, ExploreWClass, "My Documents"
Enum 3671040, WorkerW
Enum 3212492, WorkerW
Enum 1901600, msctls_statusbar32, "50 object(s) (Disk free space: 14.9 GB)"
Enum 2294932, SHELLDLL_DefView
Enum 2426026, BaseBar


You can see it can't enumerate the children of these windows like EnumChildWindows did, because we are not being recursive!

Emulating EnumChildWindows

We can use FindWindowEx to exactly emulate EnumChildWindows. This is useful not just to show how EnumChildWindows works, but we'll see later on that it can sometimes be more convenient to use our own.

Here's how it might look if coded in VB (and we could put AddressOf in a function call!)
Function EnumChildWindows(ByVal hParent As Long, _
ByVal EnumProc As Long, ByVal lParam As Long) As Long

Dim hChild As Long
Dim Continue As Boolean

Continue = AddressOf EnumProc(hParent, lParam)

hChild = FindWindowX(hParent, 0, 0, 0)
While hChild And Continue
Continue = EnumChildWindows(hChild, 0, 0) ' recursive call!
hChild = FindWindowX(hParent, hChild, 0, 0)
Wend
EnumChildWindows = Continue
End Function

You can see it's very straight-forward, it enumerates the current window, then calls itself to recursively enumerate each child window.

Really, all we have to do is substitute our enumeration procedure calls and introduce a mechanism that allows us to NOT enumerate the starting window, and we have our own EnumChildWindows function we'll call FindChildWindows.

Function FindChildWindows(ByVal hParent As Long, ByVal ChildFlag As Integer)

Dim hChild As Long
Dim Continue As Boolean

Continue = True
hChild = FindWindowX(hParent, 0, 0, 0)
If ChildFlag Then Continue = EnumChildWindow(hParent, 0)
While hChild And Continue
If Continue Then Continue = FindChildWindows(hChild, 1)
hChild = FindWindowX(hParent, hChild, 0, 0)
Wend
FindChildWindows = Continue
End Function

The ChildFlag parameter is introduced to allow the caller to prevent the function from enumerating the starting window. That's how EnumChildWindows works.

To emulate EnumChildWindows exactly pass 0, or pass 1 to enumerate the starting window, as we do here:

FindChildWindows hParent, 1

This now reproduces the complete list that we got from calling EnumChildWindow (to list the starting window), and then EnumChildWindows
Enum 6423866, ExploreWClass, "My Documents"
Enum 3671040, WorkerW
Enum 9372726, ReBarWindow32
Enum 2557106, ToolbarWindow32
Enum 984332, ComboBoxEx32, "My Documents"
Enum 1705154, ToolbarWindow32
Enum 2557142, ComboBox
Enum 1377446, Edit, "My Documents"
Enum 7931064, ToolbarWindow32
Enum 2622682, WorkerW
Enum 11470104, ToolbarWindow32
Enum 3212492, WorkerW
Enum 1901600, msctls_statusbar32, "50 object(s) (Disk free space: 14.9 GB)"
Enum 2294932, SHELLDLL_DefView
Enum 3081442, SysListView32
Enum 2294824, SysHeader32
Enum 2426026, BaseBar
Enum 1639554, ReBarWindow32
Enum 1049860, ToolbarWindow32
Enum 2819166, SysTreeView32, "Folders"

Mathimagics
12-23-2003, 02:29 AM
FindWindowEx for Enumeration

I said it could be convenient to use FindWindowEx to emulate EnumChildWindows.

One case is where we wish to be aware of the window tree structure - which child is parent to whom. Using EnumChildWindows, our enumeration function would have do a bit of work to establish the ancestry of each window (like call GetParent, look up a table, etc).

But inside EnumChildWindows (or our emulation), we can pass parent information to ourselves, making it easy to know where in the tree we are.

To illustrate, let's build a TreeView of the target window, instead of just listing the details in the debug window.

We simply combine the window information extraction code from our previous EnumChildWindow function, and combine it with the FindWindowsChild recursive logic, and we have a new Sub called BuildWindowTree.

The only assumption here is that there is a control called TreeView1.
Sub BuildWindowTree(ByVal hChild As Long, ByVal ParentKey As String)

Dim wNode As Node ' node for this window
Dim wKey As String ' key for this window

Dim wClass As String, wText As String
Dim j As Integer

wClass = Space(64)
j = GetClassName(hChild, wClass, 63)
wClass = Left(wClass, j)

wText = Space(256)
j = SendMessageS(hChild, WM_GETTEXT, 255, wText)
wText = Left(wText, j)


If Len(wText) Then
wText = wClass & ", """ & wText & """"
Else
wText = wClass
End If

wKey = "W" & hChild
If ParentKey = "" Then
Set wNode = TreeView1.Nodes.Add(, , wKey, hChild & ", " & wText)
Else
Set wNode = TreeView1.Nodes.Add(ParentKey, tvwChild, wKey, hChild & ", " & wText)
End If

Dim hParent As Long
hParent = hChild

hChild = FindWindowX(hChild, 0, 0, 0)
While hChild
BuildWindowTree hChild, wKey
hChild = FindWindowX(hParent, hChild, 0, 0)
Wend
wNode.Expanded = True
End Sub

EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum