The new vbAccelerator Site - more VB and .NET Code and Controls
Source Code
3 Code Libraries &nbsp
  Enumerating Windows and Fonts Using the API  
 

Using VB's AddressOf keyword to hook into API enumerations - the only reliable way to find all windows or fonts.

 


 NOTE: this code has been superceded by the version at the new site.



&nbsp

[Font Enumeration Demo]

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( _
&nbsp &nbsp ByVal hwnd As Long, _
&nbsp &nbsp ByVal lparam As Long _
&nbsp &nbsp ) 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( _
&nbsp &nbsp ByVal hwnd As Long, _
&nbsp &nbsp ByVal lparam As Long _
&nbsp ) As Long
Dim bStop As Boolean
&nbsp &nbsp bStop = False
&nbsp &nbsp m_cSink.EnumWindow hwnd, bStop
&nbsp &nbsp If (bStop) Then
&nbsp &nbsp &nbsp &nbsp EnumWindowsProc = 0
&nbsp &nbsp Else
&nbsp &nbsp &nbsp &nbsp EnumWindowsProc = 1
&nbsp &nbsp End If
&nbsp &nbspEnd Function

Public Function EnumerateWindows( _
&nbsp &nbsp &nbsp &nbsp ByRef cSink As IEnumWindowsSink _
&nbsp &nbsp ) As Boolean
&nbsp &nbsp If Not (m_cSink Is Nothing) Then Exit Function
&nbsp &nbsp Set m_cSink = cSink
&nbsp &nbsp EnumWindows AddressOf EnumWindowsProc, cSink.Identifier
&nbsp &nbsp 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
&nbsp &nbsp Set itmX = lvwWindows.ListItems.Add(, , WindowTitle(hwnd))
&nbsp &nbsp itmX.SubItems(1) = ClassName(hwnd)
&nbsp &nbsp itmX.SubItems(2) = hwnd
&nbsp &nbsp itmX.SubItems(3) = IsWindowVisible(hwnd)

End Sub

Private Property Get IEnumWindowsSink_Identifier() As Long
&nbsp &nbsp 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

&nbsp &nbsp ' Get the Window Title:
&nbsp &nbsp lLen = GetWindowTextLength(lHwnd)
&nbsp &nbsp If (lLen > 0) Then
&nbsp &nbsp &nbsp &nbsp sBuf = String$(lLen + 1, 0)
&nbsp &nbsp &nbsp &nbsp lLen = GetWindowText(lHwnd, sBuf, lLen + 1)
&nbsp &nbsp &nbsp &nbsp WindowTitle = Left$(sBuf, lLen)
&nbsp &nbsp End If
&nbsp &nbsp
End Function
Public Function ClassName(ByVal lHwnd As Long) As String
Dim lLen As Long
Dim sBuf As String
&nbsp &nbsp lLen = 260
&nbsp &nbsp sBuf = String$(lLen, 0)
&nbsp &nbsp lLen = GetClassName(lHwnd, sBuf, lLen)
&nbsp &nbsp If (lLen 0) Then
&nbsp &nbsp &nbsp &nbsp ClassName = Left$(sBuf, lLen)
&nbsp &nbsp 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.

[Windows Enumeration Demo]



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).

TopBack to top
Source Code - What We're About!Back to Source Code

&nbsp
 

About  Contribute  Send Feedback  Privacy

Copyright © 1998-1999, Steve McMahon ( steve@vbaccelerator.com). All Rights Reserved.
Last updated: 15 August 1999