IN THE SPOTLIGHT: MDE to MDB Conversion Service
(also supports: ACCDE to ACCDB, ADE to ADP, etc)
IN THE SPOTLIGHT: vbWatchdog
VBA error handling just got easier...
" vbWatchdog is off the chart. It solves a long standing problem of how to consolidate error handling into one global location and avoid repetitious code within applications. "
- Joe Anderson,
Microsoft Access MVP
Meet Shady, the vbWatchdog mascot watching over your VBA code →
(courtesy of Crystal Long, Microsoft Access MVP)
IN THE SPOTLIGHT: vbMAPI
An Outlook / MAPI code library for VBA, .NET and C# projects
Get emails out to your customers reliably, and without hassle, every single time.
Use vbMAPI alongside Microsoft Outlook to add professional emailing capabilities to your projects.
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:
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 (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
Have your say - comment on this article.
What did you think of 'Bring an external application window to the foreground'?
20 April 2008
This is GREAT, so complex, yet so simple to use. I have been searching for this for days!!! Thanks for the tips
06 July 2008
Like lawrence said: "so complex, yet so simple to use"
06 August 2008
13 August 2008
This is a masterpiece! Even without using the windows shell object!!! This is really cool. Thank you very much. (^_^)
|Terry Giles says...|
09 October 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!
|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!
09 September 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
Property Get strTitle() As String
strTitle = stringAppTitle
Property Let hWnd(hw As Long)
handlerAppWindow = hw
Property Get hWnd() As Long
hAppWnd = handlerAppWindow
Then the definition of the Type FindWindowParameters from the ModFindWindowLike module should be removed.
|Wayne Phillips says...|
10 September 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!
13 January 2010
great work. This is exactly was I was looking for!
Thanks a lot for sharing
29 January 2010
Thanks a lot.
As Dirk Says "This is exactly was I was looking for!"
|Rich F says...|
10 June 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.
|Mike H says...|
11 November 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.
|Joseph Dong says...|
10 January 2011
Excellent job. Easy to use! Thank you for posting the modules.
|Mark H says...|
28 February 2011
Absolutely excellent even if this is six years old. Still works like a champ.
09 March 2012
Good neat code. But, I'm running:
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.
09 March 2012
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.
|Phil W says...|
11 March 2012
I don't normally post comments but this piece of code is so great I have to make an exception. Fantastic. Many thanks.
26 November 2012
You just made my day. Thanks for sharing.
07 February 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 June 2013
One comment: For the wildcards to work one will need "Option Compare Text" statement.
Thanks a bunch.
11 March 2014
This code is such a game-changer!! THANKS!
Have your say...