Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A Versatile MessageBox Replacement

0.00/5 (No votes)
7 Oct 2012 1  
A MessageBox replacement with some much needed extras

MessageBoxEx form

Introduction

I am a 'programmer' who cares much about localization. As we work all over the world, this is not always easy. Fortunately, most of our clients are highly educated and speak English very well. Nevertheless, I do care about localizing our applications.

One of the things that kept irritating me was the fact that, at least under Windows™ XP, the MessageBoxes didn't care about the Thread.CurrentThread.CurrentUICulture. Whatever I set it to, the messagebox buttons would use the language of the installed version of Windows. So, having a bit of time, I decided to search for a solution. I 'Googled' and 'Googled', but none of the articles I found handled the problem to my satisfaction. So after some deliberation, I decided to write a MessageBox replacement.

And to thank you all for the many tips and tricks found on CodeProject, I decided to write this article as well.

Requirements

The replacement should satisfy a number of requirements:

  • It should offer transparent replacement for all the MessageBox methods. That is to say: if I used search and replace to change MessageBox into MessageBoxEx (the name I decided on) and added the correct reference, there should never be a problem because someone called a messagebox method my code did not support.
  • In the normal configuration, it should look as close to the real thing as possible.
  • It should detect the Thread.CurrentThread.CurrentUICulture and be able to change the button captions accordingly.

And, having decided to write my own messagebox, I liked to include a number of additions that were missing from Microsoft's version. They were:

  • The possibility to centre the message on the calling form, rather than in the centre of the screen.
  • It should be possible to specify a time-out, after which the default action would be taken. Since some of our programs are very computation intensive, we tend to start them when we leave the office. When you come back the next morning, it's very frustrating to see that instead of having completed, the program has been waiting for some user confirmation all night.
  • Why have only the four icons from the Microsoft version? So I would like to specify my own, more appropriate ones.
  • Sometimes, one would like to give users the option to stop reporting the same problem over and over again. So a checkbox where one could indicate "do not show this message again" would be nice.
  • And while I was busy, why not include the possibility to change the font or the colours? And the opacity?

Future addition might be the possibility to define your own captions.

Challenges

So I started the project. Much of it was relatively straightforward. But a number of interesting problems, or better challenges, soon came to light.

Sizing the Buttons

I don't know how Microsoft does it, but when you support multiple languages (and multiple fonts), you cannot simply give the form and buttons a constant size. On the other hand, I didn't want to have the button size varying with the buttons shown. Once a language was selected, all the buttons should have the same size. Luckily, the number of texts is fairly small, so the solution was to measure all the possible captions and pick the widest to determine the button size. To give a nice appearance, I discovered that a size relative to the measured size worked best. I could have chosen to apply a fixed margin instead, but this looks better.

The code that does this:

private static System.Drawing.SizeF measureButtons(Font usedFont)
{
    SizeF maxSize = new Size(1,1);
    SizeF size;

    // Measure all button captions in current culture and find widest:

    size = measureString(Deltares.Controls.Properties.Resources.buttonTextAbort, usedFont);
            if (size.Width > maxSize.Width) maxSize = size;
.....

    // Apply padding:

    maxSize = new SizeF((int)(1.6f * maxSize.Width), (int)(1.75f * maxSize.Height));
    return maxSize;
}

Displaying Help

The next problem was displaying the help. There are a number of Show methods which cause a messagebox to add a help button. Adding the button was no problem, but how did the messagebox function without knowing which file to display? After some experimenting, I discovered that the original box probably used the information from a HelpProvider on the main form. If I did not supply one, pressing the help button did nothing. If I had one and gave it the correct HelpNamespace, a press on the help button would show the file. Okay, now I needed to mimic this behaviour. This took me deep into the System.Reflection namespace. But I did solve it! Just loop over all types in the EntryAssembly (that's the one likely to contain a HelpProvider). Then check if the type is a form. If it is, loop over its fields to find if any of them is a HelpProvider. If it is, use Activator to create an instance and GetValue on the field to get access to this HelpProvider. Check if the namespace is provided, and if it is, we are done:

Assembly ass = Assembly.GetEntryAssembly();
Type[] types = ass.GetTypes();
foreach (Type type in types)
{
    if (type.BaseType.Equals( typeof(System.Windows.Forms.Form)))
    {
        FieldInfo[] fields = type.GetFields(BindingFlags.Instance |
                             BindingFlags.Public | BindingFlags.NonPublic);
        foreach (FieldInfo fi in fields)
        {
            if (fi.FieldType.Equals(typeof(System.Windows.Forms.HelpProvider)))
            {
                // Yes, we have a form with a HelpProvider.
                // Use reflection to create an instance if it:
                object  inst = Activator.CreateInstance(type);
                System.Windows.Forms.HelpProvider hp =
                        fi.GetValue(inst) as System.Windows.Forms.HelpProvider;
                // If we have succeeded and the provider has a non-null HelpNamespace,
                // take the value as the helpfile:
                if ((hp != null) && (hp.HelpNamespace != null))
                {
                    return hp.HelpNamespace;
                }
            }
        }
    }
}

You will find the above code in the getHelpFilePath method of the MessageBoxEx class.

Getting the Extra Information In (And Out)

To pass all the extra information to a messagebox that required it (font, colours, etc.), I could have chosen to create a multitude of new overloads. Added to the 21 already existing, that would have created too many variations. So I decided to only add 21 new overloaded methods, each having one extra structure added to the list of arguments. The structure would then contain all the information needed for the extra functionality. Thus the MessageBoxExtras.

Using a Time-Out

To specify a time-out, you can enter the number of milliseconds to wait. It has to be a value between 1 and 86400000 (one day). To inform the user that the box will disappear, I display a count-down progressbar. As soon as it reaches 0, the default action as defined by the defaultButton parameter, will be taken. However the system progressbar is so ugly ... so I added a simple 'subclassed' progressbar. The whole progressbar is provided via the MessageBoxExtras, and you can use the system bar as well as the replacement. How prominent the bar is you can decide by setting the height to the correct value before passing it in.

To count down from the specified time, I used a timer. But if you use a System.Windows.Forms.Timer, the thread that is running the screen updates will be blocked so frequently, that the progress bar is not updated often enough. So I use a System.Threading.Timer. But because that timer can be running on any thread from the threadpool, you have to take extra precautions when updating the progress bar. So the code executed each tick checks if an Invoke is required and if so, uses a callback to the method

There were some other difficulties, but you can inspect the code to find out how I solved them.

The Sample Application

Even before starting the code for the MessageBoxEx class, I created a sample application to test both the original version and my new version. I simply had to know how the original behaved to be able to create my own version. I could (and should?) have made an application that tested all 42 overloaded Show methods, but decided only to test 4 of them.

Sample application

In the application, you can select which messagebox to use: the system one or my version. Of course, some options are only available when using the extended version: to use other icons, you can select two additional icons when using the extended box. Of course, this is just an example, in real life, you can supply your own 32 * 32 pixel icon. To test various fonts and colours, click on the font button or the colour labels. A dialogue will help you select your favourite. Time out (in seconds) is given via the timeout numeric input. The other two checkboxes help you specify that a checkbox should be presented or the form should centre on the owner. And then click on "Try".

After selecting an option, you can see that the correct values are returned next to the button. And if a checkbox was present, the state of that checkbox is also displayed.

I hope this messagebox replacement helps you create even nicer programs. Any suggestions, criticism and additions (more languages!) are welcome.

And don't forget: have fun coding!

History

  • 6th September, 2012 - Created the article
  • 8th October, 2012 - Fixed a number of bugs reported by various readers

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.

A list of licenses authors might use can be found here