Updated! 25 October 1998. The font enumeration sample had a bug which prevented the
full name, style and charset properties from being returned. Thanks
to Serge Baranovsky for pointing out the error and sending some better code! (Check out his
site at http://www.geocities.com/SiliconValley/Hills/9086/default.html)
Introduction
Prior to VB5, it was impossible to use the enumeration methods provided in the Windows API without
relying on a proprietary custom control. This was
a problem if you wanted to find out all the fonts installed on the system, or to determine which Windows
are loaded, because the only reliable methods you can use to do this are enumerations.
The introduction of the AddressOf operator to VB5 allows the enumeration methods to be used, although it
is not as simple as it ought to be. The sample projects provided with this article provide a robust and
useful code to access Windows and Font enumerations.
Download the EnumWindows project files (14kb)
Download the EnumFonts project files (11kb)
How Enumeration Methods Work
Windows API enumeration methods work by having an initiating function which is given the address of a
function in your program to call. The call to the initiating function then starts a background process
which repeatedly calls the function you have provided with another item until all items are exhausted
or your function returns False, which indicates you don't want any further items.
Providing a Function Address
You can provide an address to a function in a VB application by using the Address Of operator. This takes
the function name as a parameter and returns a long pointer. Whilst I am most happy this has been
at last provided (I'm a bit sad that way), there is a unfortunately a strict limitation on
where you can put a function you want to use with AddressOf. The function has to reside within a .Bas module -
you cannot place it within an object (i.e. form, usercontrol or class). So if you want your enumeration to
return values to an instance of an object, you either have to hack around with global variables and methods
to get the information between the module and your object (urgh!) or somehow tell the module about the
object instance that is calling this.
An Attempt to Code Around This Elegantly
My code to work around this uses the Implements feature to specify a standard interface your object
will use to interface with the module. Here is a brief description of how it works, using the EnumWindows
call as an example.
The EnumWindows API call causes Windows to call your specied function for each window in the system,
providing the hWnd of the window. The declare for this function is as follows:
Public Declare Function EnumWindows Lib "user32" (ByVal lpEnumFunc As Long, ByVal lparam As Long) As Long
The function passed via lpEnumFunc must have the same parameters as Windows expects, otherwise when
it calls it you will either get a crash or a stack fault. In this case, the parameters are
the hWnd of the window, and the lparam value passed in when EnumWindows was first called. In addition,
the function should be declared as a long so you can return True or False back to Windows to tell it
whether to continue enumeration or not:
Private Function EnumWindowsProc( _
    ByVal hwnd As Long, _
    ByVal lparam As Long _
    ) As Long
Having set these up in a module, you then want to provide a method to initiate the Windows
enumeration, and to allow the calling object to get the items returned by the enumeration.
I do this by first setting up an interface class which specifies what the calling object must
do in order to respond to the enumeration. In this case, the calling object should be
notified with the hWnd whenever a new window is provided by EnumWindowsProc, and should have the
ability to stop the enumeration. I also allow the object to specify its own Identifier number
to pass into the lParam value of EnumWindows via an Identifier property get:
Public Sub EnumWindow(ByVal hwnd As Long, ByRef bStop As Boolean)
End Sub
Public Property Get Identifier() As Long
End Property
This class just specifies the interface to the EnumWindows method, so it doesn't have any code
in it. Any code you want to run in these methods or Property Gets must be coded in the calling
object itself. In my code, I call it IEnumWindowsSink because the object which is going to
implement it is the 'sink' for EnumWindows calls.
The module to enumerate the windows can then be set up as follows:
Private m_cSink As IEnumWindowsSink
Private Function EnumWindowsProc( _
    ByVal hwnd As Long, _
    ByVal lparam As Long _
  ) As Long
Dim bStop As Boolean
    bStop = False
    m_cSink.EnumWindow hwnd, bStop
    If (bStop) Then
        EnumWindowsProc = 0
    Else
        EnumWindowsProc = 1
    End If
   End Function
Public Function EnumerateWindows( _
        ByRef cSink As IEnumWindowsSink _
    ) As Boolean
    If Not (m_cSink Is Nothing) Then Exit Function
    Set m_cSink = cSink
    EnumWindows AddressOf EnumWindowsProc, cSink.Identifier
    Set m_cSink = Nothing
End Function
You are now in a position to use this from any form or class. By telling the form/class to implement the
IEnumWindowsSink methods, VB will automatically put the EnumWindow sub and Identifier Property Get into
the code, requiring you to code them. Here is a sample showing how to find all
the Windows on the system from a form. Here the items are placed into a ListView control called lvwWindows
with 4 columns:
Implements IEnumWindowsSink
Private Sub IEnumWindowsSink_EnumWindow(ByVal hwnd As Long, bStop As Boolean)
Dim itmX As ListItem
    Set itmX = lvwWindows.ListItems.Add(, , WindowTitle(hwnd))
    itmX.SubItems(1) = ClassName(hwnd)
    itmX.SubItems(2) = hwnd
    itmX.SubItems(3) = IsWindowVisible(hwnd)
End Sub
Private Property Get IEnumWindowsSink_Identifier() As Long
    IEnumWindowsSink_Identifier = Me.hwnd
End Property
The definitions of the functions to get a Window's title, class and visibility from a hWnd are as follows:
Public Declare Function IsWindowVisible Lib "user32" (ByVal hwnd As Long) As Long
Public Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Public Declare Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hwnd As Long) As Long
Public Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Long) As Long
Public Function WindowTitle(ByVal lHwnd As Long) As String
Dim lLen As Long
Dim sBuf As String
    ' Get the Window Title:
    lLen = GetWindowTextLength(lHwnd)
    If (lLen > 0) Then
        sBuf = String$(lLen + 1, 0)
        lLen = GetWindowText(lHwnd, sBuf, lLen + 1)
        WindowTitle = Left$(sBuf, lLen)
    End If
   
End Function
Public Function ClassName(ByVal lHwnd As Long) As String
Dim lLen As Long
Dim sBuf As String
    lLen = 260
    sBuf = String$(lLen, 0)
    lLen = GetClassName(lHwnd, sBuf, lLen)
    If (lLen 0) Then
        ClassName = Left$(sBuf, lLen)
    End If
End Function
The windows enumeration sample shows the methods described above, and additionally provides
a class which you can use to find a window based on its partial title or class name.
The font enumeration sample is very similar in structure, but the enumeration is made slightly
more tricky because for each font windows returns a long pointer to a ENUMLOGFONTEX structure.
You have to copy the memory this is pointing to into a VB structure before you can make use
of it. Check the code to see how it works! This code is also incorporated into the
Owner Draw Combo and ListBox control to provide a neat
font picker - well worth looking at (in my opinion).
Back to top Back to Source Code
|