WizMsg(): A Simple Way to Create a MsgBox with Bold Text

It's not possible to add formatting to a standard message box in VBA.

However, using the hidden WizHook object in Access, we gain the ability to easily create message boxes with a leading line of bold text.  We can do this without relying on API calls or other advanced trickery.

The message box is very similar to the built-in version, with a few key differences:

  • WizMsgBox treats the @ symbol as a paragraph separator
  • First paragraph of the message box appears in bold
  • Up to three distinct paragraphs may be included

As this is an undocumented internal function that was never meant to see the light of day, it takes an...umm...eccentric approach to parsing the message text.  The at sign (@) is used as a paragraph separator for...reasons. ¯\_ (ツ)_/¯

The Starting Point

I used the following code to test exactly how the @ symbols get treated by WizMsgBox:

Public Sub TestWizMsgBox()
    With WizHook
        .Key = 51488399
                
        Dim s As String
        s = "BoldLine1 @ @": .WizMsgBox s, "_" & s & "_", 0, 0, ""
        s = "BoldLine1 @ @ ": .WizMsgBox s, "_" & s & "_", 0, 0, ""
        s = "BoldLine1 @@": .WizMsgBox s, "_" & s & "_", 0, 0, ""
        s = "BoldLine1@@": .WizMsgBox s, "_" & s & "_", 0, 0, ""
    End With
End Sub

If you run this code, you'll see it produces a surprising variety of results.  

The Approach

Trying to understand and remember the arbitrary rules for the special @ symbol handling seemed destined to cause frustration and lead to easily-missed bugs.

So, instead of calling WizMsgBox directly in my code, I created a wrapper function that encapsulates the @-handling complexity.

A Note About Parameter Order

One thing you may notice is that–unlike the standard MsgBox–the dialog's Title (A) is the first parameter and (B) is required.

I agonized over this difference, but ultimately decided it made sense since I have separate optional arguments for lines 2 and 3.  It didn't feel right to put the title after both of the optional lines, especially since I don't anticipate Line3 will be called very often.  And putting the Title between lines 2 and 3 seemed like the worst sort of compromise.  

In the end, I'm happy with how it turned out.  The arguments appear on the dialog box from top to bottom:

  • Title
  • Line 1
  • Line 2
  • Line 3
  • Button Styles

Obviously, you can change the order of the arguments as you see fit.

The Optional Values of Line2 and Line3

As the comments explain, these values are not typos.

  • Line2's default value is a single space
  • Line3's default value is a zero-length string

I arrived at these values through trial and error.  See the bottom of the article for a screenshot of how various message boxes appear based on which lines are provided and which are not.

The WizMsg Function

As with many WizHook methods, we first need to unlock the WizHook object by setting its key to the magic value of 51488399.  Rather than do this directly, I call my Wiz() convenience function, which handles applying the magic value when needed.

' ----------------------------------------------------------------
' Procedure : WizMsg
' Date      : 10/28/2022
' Author    : Mike Wolfe
' Source    : https://nolongerset.com/wizmsg/
' Purpose   : Wrapper around the WizHook.WizMsgBox function
'               for improved code readability and reliability.
' Notes     - The optional values of Line2 and Line3 are not typos:
'               - Line2 is a single space
'               - Line3 is a zero-length string
'           - This provides us with the most intuitive results when
'				the calling code chooses not to provide either or both.
' ----------------------------------------------------------------
Public Function WizMsg(Title As String, _
        BoldLine1 As String, _
        Optional Line2 As String = " ", _
        Optional Line3 As String = "", _
        Optional MsgBoxStyle As VbMsgBoxStyle = vbOKOnly) As VbMsgBoxResult
    With Wiz
        WizMsg = .WizMsgBox(BoldLine1 & "@" & _
                            Line2 & "@" & _
                            Line3, Title, MsgBoxStyle, 0, "")
    End With
End Function



' Wiz() function included to allow easy copy-pasting of code sample
' Source    : https://nolongerset.com/wiz-wizhook-helper/
Private Function Wiz() As Access.WizHook
    Static WizHookInitialized As Boolean
    
    If Not WizHookInitialized Then
        'Most WizHook methods only work after this key has been applied:
        Const MagicWizHookKey As Long = 51488399
        WizHook.Key = MagicWizHookKey
        
        'The key only needs to be applied once per session
        WizHookInitialized = True
    End If
    
    Set Wiz = WizHook
End Function

Sample Usage

I used the following test routine to see the impact of setting or not setting the three line arguments: BoldLine1, Line2, and Line3.

Public Sub TestWiz()
    Dim x As String, y As String, z As String
    x = "Line1": y = " ": z = "": WizMsg "x" & x & "x y" & y & "y z" & z & "z", x, y, z
    x = "Line1": y = "Line2": z = "": WizMsg "x" & x & "x y" & y & "y z" & z & "z", x, y, z
    x = "Line1": y = " ": z = "Line3": WizMsg "x" & x & "x y" & y & "y z" & z & "z", x, y, z
    x = "Line1": y = "Line2": z = "Line3": WizMsg "x" & x & "x y" & y & "y z" & z & "z", x, y, z
    
    x = "": y = " ": z = "": WizMsg "x" & x & "x y" & y & "y z" & z & "z", x, y, z
    x = "": y = "Line2": z = "": WizMsg "x" & x & "x y" & y & "y z" & z & "z", x, y, z
    x = "": y = " ": z = "Line3": WizMsg "x" & x & "x y" & y & "y z" & z & "z", x, y, z
    x = "": y = "Line2": z = "Line3": WizMsg "x" & x & "x y" & y & "y z" & z & "z", x, y, z
End Sub

There are eight samples that represent all possible combinations of these three strings being either populated or not (2^3 = 8).  I've included the sample output below as a handy reference.