Recipe 11.10 Close a Running Windows Application

11.10.1 Problem

As part of some of your large Access applications, you often allow users to start other Windows tools (Notepad, Calculator, Calendar, etc.); once those tools are open, your application doesn't touch them. Some users have complained about all the "junk" left over once your application closes. Is there some way you can close another window from your Access application? That way, on the way out you can close any tools your application has opened.

11.10.2 Solution

The Solution in Recipe 11.9 demonstrated the retrieval of a list of all the running Windows applications' captions, class names, and window handles. Once you know that information, it's easy to close an application: given a window handle, simply tell it to close. Using the Windows API PostMessage function, you can close any window at any time. Of course, some applications (those that support Automation; see Chapter 12 for more information) allow themselves to be closed programmatically without using the Windows API. Other applications that don't support Automation will require either the API method described here, or SendKeys, which is unreliable at best.

Load and run frmListWindows from 11-10.MDB. This form, shown in Figure 11-12, is similar to the sample form in the Solution in Recipe 11.9 with the addition of the Stop App button, which lets you close the selected window. Try a few; you can even close Access this way, if you want.

Figure 11-12. frmListWindows includes a Stop App button

Some top-level windows shouldn't be closed?you should never include a form like this as part of an end-user application. On the other hand, given an array of window captions and handles, you could programmatically decide which window to close and close it yourself from within your application. This form is a demonstration of the power of the method, not a tool you'd actually use.

To use this functionality in your own applications, follow these steps:

  1. Import the modules basWindowList (if you haven't already for the Solution in Recipe 11.9) and basCloseWindows.

  2. Follow the steps listed in the Solution in Recipe 11.9 to create and fill in the array of top-level windows.

  3. Decide which window you want to close. Windows sometimes appends document names to the application name (e.g., "Microsoft Word?11-10.DOC"), so check against just the first portion of the window name in your array. For example:

    For intI = 0 To intCount - 1
       If Left$(atypWindowList(0).strCaption, 14) = "Microsoft Word" Then
          ' You found a match. Do something.
       End If
    Next intI
  4. When you've found the item you want to close, use the acbCloseWindow function, passing to it the handle of the window you care about:

    If acbCloseWindow(atypWindowList(intI).hWnd) = 0 Then
       ' If you got 0 back, it got the message!
    End If

11.10.3 Discussion

The acbCloseWindow function calls the PostMessage API function. By posting a message to a particular window, you are telling it to do something, but you don't bother waiting for a response. (The corresponding API function, SendMessage, does cause you to wait for a response. You can use SendMessage if you want to stop and wait for the other application to close, but we don't recommend it.) The acbCloseWindow function sends the WM_CLOSE message to your chosen window, telling it to shut down. It's as if you quit your Windows shell program with some applications running. Your shell sends a message to each main application window to shut down because Windows is shutting down. The acbCloseWindow function, then, looks like this:

Function acbCloseWindow (ByVal hWnd As Long)

   Const WM_CLOSE = &H10

   acbCloseWindow = PostMessage(hWnd, WM_CLOSE, 0, vbNullString)
End Function

The purpose of this wrapper function that calls PostMessage is to prevent you from having to remember how to post a message to a window. It's a lot simpler to call acbCloseWindow than to call PostMessage directly.

Sending a WM_CLOSE message to a window doesn't necessarily close it. If that application has an unsaved document, it will pop up its own dialog asking what you want to do with that unsaved document. In the sample form, if this happens, the list box won't be updated correctly. Once you return from your duties with the foreign application, press the Requery button on the form to force it to search again for all open applications.