Click here to Skip to main content
15,867,686 members
Articles / General Programming / Threads

A Versatile MessageBox Replacement

Rate me:
Please Sign up or sign in to vote.
4.83/5 (22 votes)
7 Oct 2012CPOL7 min read 53.4K   2.2K   68   19
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:

C#
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:

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Systems Engineer Deltares
Netherlands Netherlands
I am a researcher/advisor with the Dutch knowledge institute Deltares. My specialty is water related ICT development. Although focusing more and more on the project management side of these matters, I still like to write some code now and then. It is fun and helps me keep in touch with our software developers.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Imagiv2-Jun-16 15:27
Imagiv2-Jun-16 15:27 
QuestionHelp doesn't kill the timer Pin
Vany31430-Oct-12 6:14
professionalVany31430-Oct-12 6:14 
AnswerRe: Help doesn't kill the timer Pin
Andriks4-Nov-12 21:49
Andriks4-Nov-12 21:49 
GeneralMy vote of 5 Pin
Anurag Gandhi15-Oct-12 1:12
professionalAnurag Gandhi15-Oct-12 1:12 
QuestionThrows an exception when you use Show(IWin32Window owner,.... Pin
Member 45608917-Oct-12 14:55
Member 45608917-Oct-12 14:55 
AnswerRe: Throws an exception when you use Show(IWin32Window owner,.... Pin
Andriks7-Oct-12 20:48
Andriks7-Oct-12 20:48 
GeneralRe: Throws an exception when you use Show(IWin32Window owner,.... Pin
Member 45608913-Nov-12 5:56
Member 45608913-Nov-12 5:56 
GeneralRe: Throws an exception when you use Show(IWin32Window owner,.... Pin
Andriks4-Nov-12 21:38
Andriks4-Nov-12 21:38 
Questionnon-English character? Pin
yoke21-Sep-12 16:45
yoke21-Sep-12 16:45 
AnswerRe: non-English character? Pin
Andriks7-Oct-12 21:03
Andriks7-Oct-12 21:03 
Yoke,

For the buttons you will first have to supply the translations you require. Add a new culture specific resource to the library's Properties/Resources and enter the translations in the string table.

For the caption and text in the box you can use any string that you can define in Unicode, which should cover any language that you can think of. So, if you call MessageBoxEx.Show("hello thế giới"), the Vietnamese characters should display okay.
Andriks

A question that sometimes drives me hazy: am I or are the others crazy? | Albert Einstein

GeneralMessagebox replacement Pin
Anaya Upadhyay12-Sep-12 22:13
professionalAnaya Upadhyay12-Sep-12 22:13 
GeneralMy vote of 5 Pin
Anaya Upadhyay12-Sep-12 22:12
professionalAnaya Upadhyay12-Sep-12 22:12 
BugDemo Crashes Pin
brother.gabriel12-Sep-12 16:44
brother.gabriel12-Sep-12 16:44 
AnswerRe: Demo Crashes Pin
AlbersC13-Sep-12 20:51
professionalAlbersC13-Sep-12 20:51 
GeneralRe: Demo Crashes Pin
Andriks7-Oct-12 19:48
Andriks7-Oct-12 19:48 
GeneralCool Pin
Brisingr Aerowing12-Sep-12 14:11
professionalBrisingr Aerowing12-Sep-12 14:11 
QuestionLet me get this straight... Pin
Vitaly Tomilov6-Sep-12 12:12
Vitaly Tomilov6-Sep-12 12:12 
AnswerRe: Let me get this straight... Pin
Andriks6-Sep-12 21:07
Andriks6-Sep-12 21:07 
QuestionMy 5 Pin
Mehdi Gholam6-Sep-12 3:08
Mehdi Gholam6-Sep-12 3:08 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.