Visual Basic

Tip 4: Automatically log critical MsgBox errors.

One way to log critical MsgBox errors is by not using the standard message box provided by VBA's Interaction.MsgBox routine. When you refer to an object or a property in code, Visual Basic searches each object library you reference to resolve it. Object library references are set up in Visual Basic's References dialog box. (Open the References dialog box by selecting References from the Project menu.) The up arrow and down arrow buttons in the dialog box move references up and down in a list so that they can be arranged by priority. If two items in the list use the same name for an object, Visual Basic uses the definition provided by the item listed higher in the Available References list box. The three topmost references (Visual Basic For Applications, Visual Basic Runtime Objects And Procedures, and Visual Basic Objects And Procedures) cannot be demoted (or shuffled about). The caveat to all this prioritizing works in our favor-internal modules are always searched first.

Visual Basic 6 allows you to subclass its internal routines such as MsgBox and replace them with your own (through aggregation). Recall that in the code shown earlier (in Listing 1-1) some of the calls to MsgBox were prefixed with VBA. This explicitly scopes the call to VBA's MsgBox method via the Visual Basic For Applications type library reference. However, calls to plain old MsgBox go straight to our own internal message box routine.

A typical call to our new message box might look like this:

MsgBox "Error text in here", _
      vbYesNo + vbHelpButton + vbCritical, sMsgBoxTitle

The vbHelpButton flag is not a standard Visual Basic constant but rather an internal constant. It's used to indicate to MsgBox that it should add a Help button. Also, by adding vbCritical, we're saying that this message (error) is extremely serious. MsgBox will now log this error to a log file.

To replace MsgBox, all you have to do is write a function (an application method really) named MsgBox and give it the following signature. (The real MsgBox method has more arguments that you might also want to add to your replacement; use the Object Browser to explore the real method further.)

Public Function MsgBox _
  ( _
      ByVal isText As String _
      , Optional ByVal inButtons As Integer _
      , Optional ByVal isTitle As String  _
  )

Here's an example of a trivial implementation:

Public Function MsgBox _
  ( _
      ByVal isText As String _
      , Optional ByVal inButtons As Integer _
      , Optional ByVal isTitle As String _
  )
      Dim nResult As Integer
      nResult = VBA.Interaction.MsgBox(isText, inButtons, isTitle)
      MsgBox = nResult
  End Function

Here we're logging (implied by the call to LogError) the main message text of a message box that contains the vbCritical button style. Notice that we're using the VBA implementation of MsgBox to produce the real message box on screen. (You could use just VBA.MsgBox here, but we prefer VBA.Interaction.MsgBox for clarity.) Within your code, you use MsgBox just as you always have. Notice also that in our call to LogError we're logging away the user's response (nResult) too-"I'm sure I said 'Cancel'!"

Another good idea with any message box is always to display the application's version number in its title; that is, modify the code above to look like this:

sTitle = App.EXEName & "(" & App.Major & "." & _
                               App.Minor & "." & _
                               App.Revision & ")"
  nResult = VBA.Interaction.MsgBox(isText, inButtons, _
                                   sTitle & isTitle)

Figure 1-2 shows the message box that results from this code.

Figure 1-2 Using your version number in message boxes

Of course, you don't have to use VBA's MsgBox method to produce the message box. You could create your own message box, using, say, a form. We create our own custom message boxes because we often want more control over the appearance and functionality of the message box. For example, we often use extra buttons (such as a Help button, which is what the vbHelpButton constant was all about) in our message boxes.

One nifty way to log error events (or any other event you might consider useful) is to use the App object's LogEvent method, which logs an event to the application's log target. On Windows NT platforms, the log target is the NT Event Log; on Windows 9x machines, the log target writes to a file specified in the App.LogPath property. By default, if no file is specified, events are written to a file named VBEVENTS.LOG.

This code

Call App.LogEvent("PeetM", vbLogEventTypeInformation)
  Call App.LogEvent(Time$, vbLogEventTypeError)

produces this output in the log:

Information Application C:\WINDOWS\vbevents.log: Thread ID:
      -1902549 ,Logged: PeetM
  Error Application C:\WINDOWS\vbevents.log: Thread ID:
      -1902449 ,Logged: 15:11:32

Interestingly, App.LogPath and App.LogMode are not available at design time and are available as read-only at run time, so how do you set them? You set them with App.StartLogging. A disadvantage to these routines is that App.LogEvent is available only in a built executable-not very useful for debugging in the Integrated Development Environment (IDE)! Now the good news: you can improve on this behavior by using the Win32 API directly from your application to log events to the NT Event Log (under Windows NT) or to a file (under Windows 9x). If you're going to log events this way I would suggest that you do so by ignoring the advice given in the Support Online article, HOWTO: Write to the NT Event Log from Visual Basic. (You can find this article by searching for article ID Q154576 on Microsoft's web site, www.microsoft.com.) Instead, wrap the necessary API calls (steal the code required from the HOWTO article) within a replacement App object that is contained within an ActiveX DLL (to which you have a reference in your project). This means that you'll still use App.LogEvent and the other routines, but instead of calling into the "real" App object, you're calling into the one you've provided in the DLL (which is compiled, of course). You can write this DLL so that you can easily change App.LogFile or any other routine (if you're running Windows 9x).