Introduction
This article demonstrates how to write a replacement for Visual Basic's MsgBox
. The library allows you to display informational and interactive dialog boxes with a variety of standard icons and system sounds, as well as different button combinations for obtaining user feedback. The form also has a link which allows users to print the dialog.
Why Roll Your Own?
I started working on this because the standard MsgBox
bugged me. I had little control over the actual image used on the dialog, and no control over how the text was formatted. I was also getting tired of users coming to me with "I got an error message, but I don't remember what it said, and I clicked OK, so now it's gone." By writing my own, I can select icons that fit in with my application's overall design and make it easier to get user feedback.
The Dialog Form
The form itself is quite simple. The border style is set to FixedDialog
, and the minimize and maximize buttons are hidden. The choice of border style automatically hides the form's system icon; I will set it manually in the form's constructor. In the upper left hand corner is a PictureBox
control sized at 48 x 48, the same as the images I will be using. Main and Question are Label
controls that will hold different pieces of text; Print this message looks like a link, but is actually just another Label
. The three Button
controls are ordered right to left.
To accommodate text of varying length, Message and Question are set to AutoSize = True
. We don't want the message box to be uncomfortably huge, though, so we set the MaximumSize
property:
With this, the label can grow to a maximum width of 450 pixels; setting the max height to zero means the label can be as high as it needs to be. The Framework is polite enough to break words on spaces, so the text will remain readable.
The code for the form is pretty basic. There is a Sound
property which holds the SystemSound
played from the form's OnShown
event. The SizeForm
method arranges the controls and resizes the form so it looks nice, and the Load
and FormClosing
events are captured to manage the dialog stuff.
Imports System.Drawing
Imports System.Drawing.Printing
Imports System.Media
Friend Class ShowMsgForm
#Region " Storage "
Private _Sound As SystemSound
#End Region
#Region " Properties "
Friend Property Sound() As SystemSound
Get
Return _Sound
End Get
Set(ByVal value As System.Media.SystemSound)
_Sound = value
End Set
End Property
#End Region
#Region " Constructors "
Public Sub New()
InitializeComponent()
Me.Text = ""
Me.Icon = My.Resources.DefaultIcon
End Sub
#End Region
#Region " Event handlers "
Private Sub ShowMsgForm_FormClosing(ByVal sender As Object, _
ByVal e As System.Windows.Forms.FormClosingEventArgs) _
Handles Me.FormClosing
If e.CloseReason = CloseReason.UserClosing _
AndAlso Me.AcceptButton IsNot Nothing Then
Me.DialogResult = CType(Me.AcceptButton, Button).DialogResult
End If
End Sub
Private Sub ShowMsgForm_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles Me.Load
Dim Btn As Button = TryCast(Me.AcceptButton, Button)
If Btn IsNot Nothing AndAlso Btn.Visible Then
Btn.Select()
Else
Me.Button1.Select()
End If
End Sub
#End Region
#Region " Methods "
Protected Overrides Sub OnShown(ByVal e As System.EventArgs)
If _Sound Is Nothing Then
SystemSounds.Asterisk.Play()
Else
_Sound.Play()
End If
MyBase.OnShown(e)
End Sub
Public Sub SizeForm()
Dim NewHeight As Integer = 0
Dim NewWidth As Integer = 0
NewWidth = Math.Max(TextLabel.Width, QuestionTextLabel.Width) _
+ TextLabel.Left + 4
If NewWidth < 275 Then NewWidth = 275
Button1.Left = NewWidth - Button1.Width - 4
Button2.Left = Button1.Left - Button2.Width - 4
Button3.Left = Button2.Left - Button3.Width - 4
If QuestionTextLabel.Text = "" Then
PrintLabel.Top = TextLabel.Top + TextLabel.Height + 8
Else
QuestionTextLabel.Top = TextLabel.Top + TextLabel.Height + 8
PrintLabel.Top = QuestionTextLabel.Top + _
QuestionTextLabel.Height + 8
End If
Button1.Top = PrintLabel.Top + PrintLabel.Height + 8
Button2.Top = Button1.Top
Button3.Top = Button1.Top
NewHeight = Math.Max(Button1.Top + Button1.Height, _
MessagePictureBox.Top + MessagePictureBox.Height) + 4
Me.SetClientSizeCore(NewWidth, NewHeight)
End Sub
#End Region
End Class
For brevity, I have cut the code that prints the dialog; you will need to download the source code linked above. Basically, it takes a snapshot of the whole form, including the title bar, and prints it out. It is boilerplate code that can be found easily on the web.
Support Code
Along with the form, there is some support code.
Public Enum ShowMsgImage
Info
Alert
Confirm
Critical
Security
UnderConstruction
End Enum
Public Enum ShowMsgButtons
OkOnly
ContinueCancel
YesNo
YesNoCancel
End Enum
Public Enum ShowMsgDefaultButton
Button1
Button2
Button3
End Enum
ShowMsgImage
indicates which icon will be displayed on your message box. ShowMsgButtons
provides information on what buttons will be displayed, and ShowMsgDefaultButton
flags the button that will be the default button on the form. If the message box is closed without any of the buttons being clicked, the dialog will return the result of the default button. In the source code, you will also find some additional code that supports printing the form.
ShowMsg - Simple Messages
So finally, we come to the main course, the ShowMsg
function. The eight overloads will configure and display instances of ShowMsgForm
and return the dialog result.
The first set of overloads generate an information only dialog. With the work method, the developer provides the text to be displayed, selects an icon, and provides text for the dialog's title bar.
Public Function ShowMsg(ByVal Text As String, _
ByVal Icon As ShowMsgImage, ByVal Title As String) _
As DialogResult
Dim SMF As New ShowMsgForm
SMF.Text = Title
Select Case Icon
Case ShowMsgImage.Alert
SMF.MessagePictureBox.Image = My.Resources.Warning
SMF.Sound = Media.SystemSounds.Asterisk
Case ShowMsgImage.Confirm
SMF.MessagePictureBox.Image = My.Resources.Confirm
SMF.Sound = Media.SystemSounds.Question
Case ShowMsgImage.Critical
SMF.MessagePictureBox.Image = My.Resources.NotAllowed
SMF.Sound = Media.SystemSounds.Hand
Case ShowMsgImage.Info
SMF.MessagePictureBox.Image = My.Resources.Info
SMF.Sound = Media.SystemSounds.Asterisk
Case ShowMsgImage.Security
SMF.MessagePictureBox.Image = My.Resources.Lock
SMF.Sound = Media.SystemSounds.Beep
Case ShowMsgImage.UnderConstruction
SMF.MessagePictureBox.Image = My.Resources.UnderConstruction
SMF.Sound = Media.SystemSounds.Asterisk
End Select
SMF.TextLabel.Text = Text
SMF.QuestionTextLabel.Text = ""
SMF.Button1.Visible = True
SMF.Button1.Text = "OK"
SMF.Button1.DialogResult = DialogResult.OK
SMF.Button2.Visible = False
SMF.Button3.Visible = False
SMF.SizeForm()
SMF.StartPosition = FormStartPosition.CenterScreen
Return SMF.ShowDialog
End Function
Note that the choice of icon determines the sound that will be played when the dialog is shown. Because this method is for simple messages, we hide the Question label and buttons 2 and 3. A call to SizeForm
arranges the visible elements and resizes the form. It is set to display in the center of the screen, then displayed as a modal dialog. The result of the user's click on the form provides the function's return value.
There are two simpler overloads, one without the Title
parameter and one without Title
and Icon
. In these cases, the dialog's title bar will display a default value and the icon is set to the Alert
image.
You don't need to worry about having very long messages: because we set the MaximumSize
property on the message label, the Framework will insert line breaks where needed:
ShowMsg - Interactive Messages
The next set of overloads start with two String
parameters, the message and a question. By having two separate parameters, we can create a visual break between the information and the request for feedback. The programmer then indicates which pattern of buttons to use, which of those buttons will be the default, and what text, if any, will appear in the dialog's title bar.
Public Function ShowMsg(ByVal Text As String, ByVal Question As String, _
ByVal Buttons As ShowMsgButtons, _
ByVal DefaultButton As ShowMsgDefaultButton, _
ByVal Title As String) As DialogResult
Dim SMF As New ShowMsgForm
SMF.MessagePictureBox.Image = My.Resources.Confirm
SMF.Text = Title
SMF.TextLabel.Text = Text
SMF.QuestionTextLabel.Text = Question
SMF.Sound = Media.SystemSounds.Question
Select Case Buttons
Case ShowMsgButtons.ContinueCancel
SMF.Button1.Visible = True
SMF.Button1.Text = "Cancel"
SMF.Button1.DialogResult = DialogResult.Cancel
SMF.Button2.Visible = True
SMF.Button2.Text = "Continue"
SMF.Button2.DialogResult = DialogResult.OK
SMF.Button3.Visible = False
Case ShowMsgButtons.YesNo
SMF.Button1.Visible = True
SMF.Button1.Text = "No"
SMF.Button1.DialogResult = DialogResult.No
SMF.Button2.Visible = True
SMF.Button2.Text = "Yes"
SMF.Button2.DialogResult = DialogResult.Yes
SMF.Button3.Visible = False
Case ShowMsgButtons.OkOnly
SMF.Button1.Visible = True
SMF.Button1.Text = "OK"
SMF.Button1.DialogResult = DialogResult.OK
SMF.Button2.Visible = False
SMF.Button3.Visible = False
Case ShowMsgButtons.YesNoCancel
SMF.Button1.Visible = True
SMF.Button1.Text = "Cancel"
SMF.Button1.DialogResult = DialogResult.Cancel
SMF.Button2.Visible = True
SMF.Button2.Text = "No"
SMF.Button2.DialogResult = DialogResult.No
SMF.Button3.Visible = True
SMF.Button3.Text = "Yes"
SMF.Button3.DialogResult = DialogResult.Yes
End Select
Select Case DefaultButton
Case ShowMsgDefaultButton.Button1
SMF.AcceptButton = SMF.Button1
Case ShowMsgDefaultButton.Button2
SMF.AcceptButton = SMF.Button2
Case ShowMsgDefaultButton.Button3
SMF.AcceptButton = SMF.Button3
End Select
SMF.SizeForm()
SMF.StartPosition = FormStartPosition.CenterScreen
Return SMF.ShowDialog()
End Function
By default, the dialog is set to the Question
icon and its corresponding system sound. The desired buttons are displayed and labeled, then the form's AcceptButton
is set. A call to SizeForm
arranges things, the dialog is set to display in the center of the screen, then it is shown.
There are two overloads that are similar, one without the Title
parameter and one without Title
and DefaultButton
. With these, the title bar will display a default title and the right-most button will be set as the default.
ShowMsg - Exceptions
The last two versions of ShowMsg
take an Exception
parameter and an optional ShowMsgImage
; you can find the source in the downloadable project. The work method looks at the type of the exception to provide formatting: my code recognizes DataException
, COMException
, and "Other". Having a message box that provides exception information in a friendly, easy to understand (and printable!) format has proven to be an amazingly useful bit of functionality.
ShowMsg Variations
It has also been useful to create standard message boxes, such as "Access denied" and "Under construction". These are simple wrappers around calls to an appropriate overload of ShowMsg
.
Public Function ShowAccessDenied() As DialogResult
Return ShowMsg("You do not have sufficient permission to perform requested operation.", _
ShowMsgImage.Security, "Access violation")
End Function
Room for Improvement
I designed this to be a library with all calls being made through ShowMsg
or one of the specialty wrappers, and not by creating and configuring an instance of ShowMsgForm
. This was mostly laziness on my part. It should be pretty easy to create a public version of the form, or simply copy it into your application directly; this way, you can tinker with other properties like background color and button styles.
Another nifty improvement would be to replace the Label
controls for the message and question with RichTextBox
es. You would need to format your text as RTF, but this would let you do selective bolding, underlining, and color changes.
As written, the message box will show up in the Task Bar. It might be useful to add code to OnShown
that checks to see if the dialog is hidden behind something else and, if so, flash its Task Bar object.
Conclusion
There are probably bugs in my code; if you find any, please let me know and I will get them fixed. Comments and critiques are always welcome and, as always, please vote this article up if you find it useful.
History
- Version 1 - May 6, 2010: Initial release.
Gregory Gadow recently graduated from Central Washington University with a B.S. that combined economics and statistical analysis, and currently works for the Washington Department of Fish & Wildlife as an IT developer. He has been writing code for 30 years in more than a dozen programming languages, including Visual Basic, VB.Net, C++, C#, ASP, HTML, XML, SQL, and R.