Looking to take your VBA skills further?...

Discover twinBASIC — a powerful new development platform that expands on VBA and VB6 with advanced features, modern tools, and enhanced compatibility. Perfect for those ready to elevate their projects or transition from VBA, twinBASIC lets you build on what you already know and take your applications to the next level!

Try out twinBASIC Community Edition - it's free!

Bring an external application window to the foreground

        69 votes: *****     84,908 views      23 comments
by Wayne Phillips, 03 June 2005    (for Access 97+)

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:

  1. How can we identify the window that we want to bring to the foreground?
  2. Once identified, how do we actually bring the window to the foreground?

How the Operating System identifies windows

The Windows Operating System (OS) uses 'handles' to identify all open windows in the OS environment.  Essentially a handle is basically a 32-bit number (long value) that is uniquely assigned to each open window (often referred to as hWnd).  Without a window handle you cannot manipulate external applications windows.

How can we identify the window that we want to bring to the foreground?   

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 (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 (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
				

Rate this article:  Your rating: PoorYour rating: Not so goodYour rating: AverageYour rating: GoodYour rating: Excellent


Have your say - comment on this article.

What did you think of 'Bring an external application window to the foreground'?


1.

Lawrence says...

20 Apr 2008

 
This is GREAT, so complex, yet so simple to use. I have been searching for this for days!!! Thanks for the tips

2.

Teaser-Trailer.com says...

06 Jul 2008

 
Like lawrence said: "so complex, yet so simple to use"

Thanks alot!

3.

tribs says...

06 Aug 2008

 
excellent.

4.

BUm_Lei says...

13 Aug 2008

 
This is a masterpiece! Even without using the windows shell object!!! This is really cool. Thank you very much. (^_^)

5.

Terry Giles says...

09 Oct 2008

 
This is great! I'm using it to see if the user has Outlook open as it sometimes fails to send an email via VBA if the app isn't open. The problem w/ Outlook is the window title changes depending on where the user is at (eg Inbox - Microsoft Office; Calendar - Microsoft Office). Using your FnFindWindowLike code I can tell if Outlook open & if not open it. Thanks again!

6.

Matic Kukovec says...

31 May 2009

 
Thank you for the intricate details. You really know your VB. Couldn't be happier that you gave so much effort into the explanation! Keep it up!

7.

Veaceslav says...

09 Sep 2009

 
Thanks. It is a great piece of code. I had been also looking for it for a number of days.

If I may also contibute:

- in EnumWindowProc after EnumWindowProc=0 the Exit Function statement will stop execution after the first window with appropriate window title found

- instead of using a Type for FindWindowParameters, use a new class FindWindowParameters with Properties strTitle and hWnd would allow to avoid undocumented VarPtr. A new class module FindWindowParameters would contain:

Private stringAppTitle As String 'INPUT
Private handlerAppWindow As Long 'OUTPUT

Property Let strTitle(st As String)
stringTitle = st
End Property

Property Get strTitle() As String
strTitle = stringAppTitle
End Property

Property Let hWnd(hw As Long)
handlerAppWindow = hw
End Property

Property Get hWnd() As Long
hAppWnd = handlerAppWindow
End Property

Then the definition of the Type FindWindowParameters from the ModFindWindowLike module should be removed.

8.

Wayne Phillips says...

10 Sep 2009

 
Greate suggestion, Veaceslav.

I will leave the article as is (since it's quite old now), but certainly your slightly modified solution would work fine also. Thanks for sharing!

9.

Dirk says...

13 Jan 2010

 
great work. This is exactly was I was looking for!
Thanks a lot for sharing

10.

sam says...

29 Jan 2010

 
Thanks a lot.
Great work!
As Dirk Says "This is exactly was I was looking for!"

11.

Rich F says...

10 Jun 2010

 
Excellent work! All of this code because Access (<=2003) can't give an open query or table an hWnd value and won't give it a SetFocus command!

Great research and presentation. I learned a lot today.

12.

Mike H says...

11 Nov 2010

 
Absolutely first rate 5 Stars!
Works first time just by naming the modules as stated in the comments and calling the FnSetForegroundWindow function with the apps exe name.

Have been searching for something like this for ages.

13.

Joseph Dong says...

10 Jan 2011

 
Excellent job. Easy to use! Thank you for posting the modules.

14.

Mark H says...

28 Feb 2011

 
Absolutely excellent even if this is six years old. Still works like a champ.

15.

jm says...

09 Mar 2012

 
Good neat code. But, I'm running:

?FnSetForegroundWindow("Upload*")

from the immediate window in Outlook VBA - it works with no errors; I see the window in question also "flicker" then disappear back again, and I'm left looking at the Outlook VBA window again. Also, using just the FnFindWindowLike function in the immediate window works fine: gives me the long num, or 0 if not found.

any suggestions would be appreciated.

16.

Wayne says...

09 Mar 2012

 
Hi Jm,

You can't test this function in the immediate window, because the immediate window is automatically reset as the foreground window after the line of VBA code has been executed.

17.

Phil W says...

11 Mar 2012

 
Wayne,

I don't normally post comments but this piece of code is so great I have to make an exception. Fantastic. Many thanks.

18.

Excelausorus says...

26 Nov 2012

 
You just made my day. Thanks for sharing.

19.

Chicken says...

07 Feb 2013

 
Brilliant piece of code, thanks, however...

I'm trying to mod it to show me all open windows. I have semi-done this but it returns everything (including "Start" (which is the start menu i guess) - any chance of a hand in getting all visible windows, all i really need is the code to retrieve a property of the hWnd to say whether is active or visible or something :)

But again, brilliant piece of coding matey, glad you made so many peoples day!!

20.

Rodney_McKay says...

20 Jun 2013

 
Great work!
One comment: For the wildcards to work one will need "Option Compare Text" statement.

Thanks a bunch.

21.

Anil says...

11 Mar 2014

 
AMAZING!!

This code is such a game-changer!! THANKS!

22.

Gregory Kussmann says...

20 Feb 2018

 
This is a really slick piece of code. Saved me a ton of time researching API-level calls and works perfectly for my application. Can't thank you enough for posting this.

23.

Alan Cossey says...

01 Mar 2018

 
I am using a popup form to log people into an Access database and have found no reliable way of getting that form to come to the foreground when that main Access database is launched by another (app-launching, automatically downloading new versions) Access database. Putting this code into the Open Event of the popup form brings the form to the foreground. Many thanks, Wayne.

Have your say...

Name
E-mail (e-mail address will be kept private)
Comments


Comments require approval before being displayed on this page (allow 24 hours).