This tutorial will show you how to easily bring an
external application window to the foreground by using VBA code to call
a few Windows API calls. There are essentially two problems to
this:
Fortunately the OS offers many forms of identifying the
window handles by using Windows API functions. The simplest function is FindWindow:
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
The lpWindowName input parameter is a string that
represents the exact caption of the Window you want to locate the handle
of. So for example "Calculator" would be acceptable - however what
happens if the caption of the window you want to find is dynamic? - for
example... '<MyDocumentName> Microsoft Word'... well this poses a
problem. You cannot specify wildcards in the lpWindowName
parameter (e.g. '* Microsoft Word' is not acceptable).
You could however use the lpClassName input parameter
instead, however you will need to work out what the class name is (using
a program such as Spy++) and then determine if it unique in your
application. This again isn't a good solution.
The solution is to enumerate through the list of open
windows and compare each window caption with a wildcard check manually.
To enumerate through each open window handle, we can use the EnumWindows
API function, passing a hook function that is called once for each open
window.
Public Declare Function EnumWindows Lib "user32" (ByVal lpEnumFunc As Long, ByVal lParam As Long) As Long
Using hooks is quite complex, however I will briefly
explain. We call the EnumWindows API function with the lpEnumFunc
parameter set to the address of our enumeration function (which has a
specific format - see next paragraph). The enumeration function is
also passed the custom parameter lParam that is specified in the
EnumWindows function.
' This function gets called once for each open window
Public Function EnumWindowProc(ByVal hWnd As Long, lParam As FindWindowParameters) As Long
'hWnd parameter is the window handle
'lParam is the long value we pass in to the EnumWindows function
'We can grab the window caption here and compare to our wildcard match...
EnumWindowProc = 1 ' This ensures we loop through all open windows
End Function
To get the window caption from the given handle (hWnd),
we can use the GetWindowText API function:
Public Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Usage:
Dim strWindowTitle As String
strWindowTitle = Space(260) ' We must allocate a buffer for the GetWindowText function
Call GetWindowText(hWnd, strWindowTitle, 260)
Once we have the window caption/title we can use a
"like" comparison to determine if this is the window we are looking for
based on a wildcard string. The next question is - How do we pass
in our wildcard search string so that the function knows what to search
for and without hard-coding it into the EnumWindowProc procedure.
And we must also work out how to return the window handle to the calling
function (the function that calls EnumWindows) as we shouldn't be
hard-coding any procedures into our EnumWindowProc function (since good
programming design is usually built on a good modular structure!).
This is where the lParam parameter comes in.
lParam is a 32-bit long value and is passed on to every call of
EnumWindowProc. Since we also know that pointers to structures are
also 32-bit long values then we can actually manipulate the lParam a
little and pass in a pointer to a custom structure (or 'Type').
That way we can define a custom Type with our two parameters (strWildcardMatch
input parameter and hWndFound output paramater) and use them in our
EnumWindowProc function. You could use global variables to store
these two parameters but personally I prefer a tightly integrated
solution whenever possible (and I don't consider global parameters as
tightly-integrated!). In order to pass the custom Type as a 32-bit
pointer we will use the undocumented VarPtr VB function (more info
here).
Back to EnumWindows - In VB/VBA, to get the address of a
function for use in hooks we use the AddressOf statement. For
example, we are going to use:
Call EnumWindows(AddressOf EnumWindowProc, VarPtr(OurCustomStructure))
Ok so here's our final code for our replacement for the
FindWindow API call that will accept wildcard strings... e.g. hWnd =
FnFindWindowLike("* Microsoft Word")
Option Explicit
' Module Name: ModFindWindowLike
' (c) 2005 Wayne Phillips (http://www.everythingaccess.com)
' Written 02/06/2005
Private Declare Function EnumWindows Lib "user32" _
(ByVal lpEnumFunc As Long, _
ByVal lParam As Long) As Long
Private Declare Function GetWindowText Lib "user32" _
Alias "GetWindowTextA" _
(ByVal hWnd As Long, _
ByVal lpString As String, _
ByVal cch As Long) As Long
'Custom structure for passing in the parameters in/out of the hook enumeration function
'Could use global variables instead, but this is nicer.
Private Type FindWindowParameters
strTitle As String 'INPUT
hWnd As Long 'OUTPUT
End Type
Public Function FnFindWindowLike(strWindowTitle As String) As Long
'We'll pass a custom structure in as the parameter to store our result...
Dim Parameters As FindWindowParameters
Parameters.strTitle = strWindowTitle ' Input parameter
Call EnumWindows(AddressOf EnumWindowProc, VarPtr(Parameters))
FnFindWindowLike = Parameters.hWnd
End Function
Private Function EnumWindowProc(ByVal hWnd As Long, _
lParam As FindWindowParameters) As Long
Dim strWindowTitle As String
strWindowTitle = Space(260)
Call GetWindowText(hWnd, strWindowTitle, 260)
strWindowTitle = TrimNull(strWindowTitle) ' Remove extra null terminator
If strWindowTitle Like lParam.strTitle Then
lParam.hWnd = hWnd 'Store the result for later.
EnumWindowProc = 0 'This will stop enumerating more windows
Else
EnumWindowProc = 1
End If
End Function
Private Function TrimNull(strNullTerminatedString As String)
Dim lngPos As Long
'Remove unnecessary null terminator
lngPos = InStr(strNullTerminatedString, Chr$(0))
If lngPos Then
TrimNull = Left$(strNullTerminatedString, lngPos - 1)
Else
TrimNull = strNullTerminatedString
End If
End Function
How do we actually bring the window to the
foreground?
Now that we can use our FnFindWindowLike function to
find a specific window handle by the window caption, we can now bring
the window to the foreground as follows:
' Module Name: ModSetForegroundWindow
' (c) 2005 Wayne Phillips (http://www.everythingaccess.com)
' Written 02/06/2005
Private Declare Function SetForegroundWindow Lib "user32" _
(ByVal hwnd As Long) As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" _
(ByVal hwnd As Long, _
lpdwProcessId As Long) As Long
Private Declare Function IsIconic Lib "user32" _
(ByVal hwnd As Long) As Long
Private Declare Function ShowWindow Lib "user32" _
(ByVal hwnd As Long, _
ByVal nCmdShow As Long) As Long
Private Declare Function AttachThreadInput Lib "user32" _
(ByVal idAttach As Long, _
ByVal idAttachTo As Long, _
ByVal fAttach As Long) As Long
Private Declare Function GetForegroundWindow Lib "user32" _
() As Long
Private Const SW_RESTORE = 9
Private Const SW_SHOW = 5
Public Function FnSetForegroundWindow(strWindowTitle As String) As Boolean
Dim MyAppHWnd As Long
Dim CurrentForegroundThreadID As Long
Dim NewForegroundThreadID As Long
Dim lngRetVal As Long
Dim blnSuccessful As Boolean
MyAppHWnd = FnFindWindowLike(strWindowTitle)
If MyAppHWnd <> 0 Then
'We've found the application window by the caption
CurrentForegroundThreadID = GetWindowThreadProcessId(GetForegroundWindow(), ByVal 0&)
NewForegroundThreadID = GetWindowThreadProcessId(MyAppHWnd, ByVal 0&)
'AttachThreadInput is used to ensure SetForegroundWindow will work
'even if our application isn't currently the foreground window
'(e.g. an automated app running in the background)
Call AttachThreadInput(CurrentForegroundThreadID, NewForegroundThreadID, True)
lngRetVal = SetForegroundWindow(MyAppHWnd)
Call AttachThreadInput(CurrentForegroundThreadID, NewForegroundThreadID, False)
If lngRetVal <> 0 Then
'Now that the window is active, let's restore it from the taskbar
If IsIconic(MyAppHWnd) Then
Call ShowWindow(MyAppHWnd, SW_RESTORE)
Else
Call ShowWindow(MyAppHWnd, SW_SHOW)
End If
blnSuccessful = True
Else
MsgBox "Found the window, but failed to bring it to the foreground!"
End If
Else
'Failed to find the window caption
'Therefore the app is probably closed.
MsgBox "Application Window '" + strWindowTitle + "' not found!"
End If
FnSetForegroundWindow = blnSuccessful
End Function