Introduction

Problem

I was writing some custom installer code for a Visual Studio installer project and had to display messages to the user in my custom installer class. The problem was that when using the standard System.Windows.Forms.MessageBox.Show method the message box was appearing behind the main installer window. There are no options on the MessageBox class to alter this behavior. I could just see the support lines lighting up with calls of the install hanging.

Solution

The solution was to make the window owning the MessageBox a TopMost window. One difficulty with that was that the custom installer class does not have a reference to the main installer window and has no UI of its own. So, a new form had to be created within my custom installer class which could be the owner of the MessageBox. Setting this form as a TopMost window causes the MessageBox to be a TopMost window as well. Thus, the messages displayed by my custom installer would always be visible. Special actions are taken to ensure this new form is not visible since it would merely be a distraction to the user.

This solution was packaged into a wrapper class called TopMostMessageBox.

Using the code

Here is how you could use the TopMostMessageBox in your code.

TopMostMessageBox.Show(
"This will appear in a message box that is a topmost window",
"Title", MessageBoxButtons.AbortRetryIgnore);

Here is the declaration of the TopMostMessageBox class.

staticpublicclass TopMostMessageBox
{
staticpublic DialogResult Show(string message)
{
return Show(message, string.Empty, MessageBoxButtons.OK);
}
staticpublic DialogResult Show(string message, string title)
{
return Show(message, title, MessageBoxButtons.OK);
}
staticpublic DialogResult Show(string message, string title,
MessageBoxButtons buttons)
{
// Create a host form that is a TopMost window which will be the
// parent of the MessageBox.
Form topmostForm = new Form();
// We do not want anyone to see this window so position it off the
// visible screen and make it as small as possible
topmostForm.Size = new System.Drawing.Size(1, 1);
topmostForm.StartPosition = FormStartPosition.Manual;
System.Drawing.Rectangle rect = SystemInformation.VirtualScreen;
topmostForm.Location = new System.Drawing.Point(rect.Bottom + 10,
rect.Right + 10);
topmostForm.Show();
// Make this form the active form and make it TopMost
topmostForm.Focus();
topmostForm.BringToFront();
topmostForm.TopMost = true;
// Finally show the MessageBox with the form just created as its owner
DialogResult result = MessageBox.Show(topmostForm, message, title,
buttons);
topmostForm.Dispose(); // clean it up all the way
return result;
}
}

Points of Interest

I tried to make this wrapper mimic the existing MessageBox.Show method, but many overrides of this method are not included in this simple wrapper. However, they can easily be added if you need them.

The hidden form is created off the visible screen to prevent any flickering. This is done by finding the size of the virtual desktop and positioning the hidden form just off past the right side of it. I do not use the physical screen size to account for multiple monitors.

The combination of Focus(), BringToFront(), and TopMost seemed to be needed to get the MessageBox to show up properly and have input focus.

To clean up the hidden form completely I call Dispose on the form.

Other Options

I could have used the MessageBoxIndirect function to accomplish the same thing. However, this is a bit simpler as it does not involve reproducing any Win32 structures.

History

1.0 - 21 Apr 07 - First Posting

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Comments and Discussions

I tried it and it works.
I wonder if there is simpler solution. It just wants to bring a message to the top. Incredibly, it needs so long codes. C# should provide a parameter in MessageBox.Show() to do that job.

This works perfectly as is, but I would like to also bring up the alternative using MB_TOPMOST

This is all you have to do, no using wrappers or new forms:
MessageBox.Show("Message Text", "Header", MessageBoxButtons.OK, MessageBoxIcon.None, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0x40000);

If I open my program in my right-hand screen the MessageBox would ordinarily have appeared on that same screen (albeit BEHIND the other form!) but now it appears on the left screen. (although it's now in front of everything. )

Does anyone have an idea of how to change this?

I don't want to spend too much time on it because I doubt any of our users will have more than one screen anyway. It's just obvious to me because I remember how it used to show.

- Ken Lyon
"Before you judge a man, walk a mile in his shoes.
By that time, who cares?
You're a mile away, and you've got his shoes."

I'm glad to have found this solution. My situation was a bit frustrating with my splash screen hiding an important MessageBox. Oddly enough, Visual Studio shows the MessageBox on top, but running the program directly exhibits the problem of the MessageBox being behind.

I noticed something that I thought would be worth pointing out. The topmostForm appears in the task bar by default. The simplest solution to this would be to add the following line:

topMostForm.ShowInTaskbar = false;

An alternative would be to set topMostForm.Text to match the title of the MessageBox. This would create the illusion of the task bar entry corresponding to the MessageBox, although it's really topMostForm. That would be the visual equivalent of showing the MessageBox with no owner.

Maybe all this is too much detail for most people. I just tend to notice these things.

- Ken Lyon
"Before you judge a man, walk a mile in his shoes.
By that time, who cares?
You're a mile away, and you've got his shoes."

Chuck Norris has the greatest Poker-Face of all time. He won the 1983 World Series of Poker, despite holding only a Joker, a Get out of Jail Free Monopoloy card, a 2 of clubs, 7 of spades and a green #4 card from the game UNO.In the movie "The Matrix", Chuck Norris is the Matrix. If you pay close attention in the green "falling code" scenes, you can make out the faint texture of his beard.Chuck Norris actually owns IBM. It was an extremely hostile takeover.

I tried the two suggested workarounds posted earlier and neither worked. I needed to show a messagebox on startup sometimes from the Main() sub before doing Application.Run(mainform). Vance's hidden form worked perfectly. Otherwise, the messagebox remained hidden behind the last focused app's window, e.g., Windows Explorer. Thanks Vance.

Oh man, I soo wanted this to work... but it doesn't for me. When I use this, the dialog never shows up. I am creating the dialog in a new thread (I want a *non-modal* topmost dialog!), so maybe that's different than what you're doing.... Just using

The message box will not show or behave correctly when spawn from a different thread. In your specific case, you need to invoke (call) the message box on the thread used to create the new form. You do this by calling the Form's Invoke method, which executes a delegate on the thread that owns the control's underlying window handle. This will work:

Dim oForm AsNew Form
oForm.TopMost = True
oForm.ShowInTaskbar = False'other form initialization
oForm.Invoke(...) 'where ... is a delegate to a method that shows the message box.

This is the basic idea.

Private Delegate Sub AlarmHandler()
Private Sub DoAlarm()
'message box and other user-interface statements
End Sub
''' <remarks>
''' This method is called from the background thread of a System.Threading.Timer object (non-Form timer).
''' </remarks>
Private Sub CheckForAlarm(ByVal ignoredOnPurpose As Object)
If (App.ReportingDate.CompareTo(moAlarmTime) > 0) And (mfAlarmSet = True) Then
If Me.InvokeRequired Then
Me.Invoke(New AlarmHandler(AddressOf DoAlarm))
Else
DoAlarm
End If
End If
End Sub