Click here to Skip to main content
Click here to Skip to main content

A versatile MessageBox replacement

By , 7 Oct 2012
 

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 it's 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 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 your 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

  • September 6, 2012 - Created the article.
  • October 8, 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)

About the Author

Andriks
Systems Engineer Deltares
Netherlands Netherlands
Member
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.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionHelp doesn't kill the timermemberVany31430 Oct '12 - 6:14 
Hi,
great job You've made!
One hint - wouldn't be better to pause or kill the timer when the Help button is pressed?
Second - it seems the Help button has it's localizations, but still displays "Help"
Third - would it be possible to choose "no localization" and use system strings?
htanks, Vany
AnswerRe: Help doesn't kill the timermemberAndriks4 Nov '12 - 21:49 
1) Yes, you have a good one there. When I have time to create a new version I will suspend or kill the timer.
2) Another bug. If you want to fix it yourself, go to routine getButtonCaptions in MessageBoxEx.cs and add another else if statement for buttonTextHelp.
3) Should be possible, but for the moment I do not have the time to dig out where to get the system strings for these buttons.
Andriks
 
A question that sometimes drives me hazy: am I or are the others crazy? | Albert Einstein

GeneralMy vote of 5memberAnurag Gandhi15 Oct '12 - 1:12 
Nice One!!
QuestionThrows an exception when you use Show(IWin32Window owner,....memberMember 45608917 Oct '12 - 14:55 
Using the function MessageBoxEx.Show(this,"text","caption") throws a ArgumentException -> "Top-level control cannot be added to a control."
AnswerRe: Throws an exception when you use Show(IWin32Window owner,....memberAndriks7 Oct '12 - 20:48 
Yes, a bug indeed. Which shows you cannot test enough. I repaired the bug and added the code to check these overloads too. I will post these changes here shortly.
Andriks
 
A question that sometimes drives me hazy: am I or are the others crazy? | Albert Einstein

GeneralRe: Throws an exception when you use Show(IWin32Window owner,....memberMember 45608913 Nov '12 - 5:56 
Any idea when we'll see that fix? Or what the fix is? I'm having a similar issue when I use try to use messageboxex in a multi threaded app...
GeneralRe: Throws an exception when you use Show(IWin32Window owner,....memberAndriks4 Nov '12 - 21:38 
It should be fixed in the October 8 version. At least: I cannot reproduce the problem in that version. As to the problems with a multi-threaded app: do you launch the dialog from a background thread, while the extra's are created on the main thread? That's the only scenario where I can imagine some problems. Send me an small example that shows the problem.
Andriks
 
A question that sometimes drives me hazy: am I or are the others crazy? | Albert Einstein

Questionnon-English character?memberyoke21 Sep '12 - 16:45 
excellent idea!
i tried both of your source & demo project.
set the culture to non-English alphabets inside the demo and source.
it still displays only English character in the messagebox.
 
also tried it with the PC region set to Thai, Vietnamese or Korean.
hit the TRY button and still came up as English.
 
love your screenshot.
how do you get it to display non-English characters though?
AnswerRe: non-English character?memberAndriks7 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 replacementmemberAnaya Upadhyay12 Sep '12 - 22:13 
Your article really help me a lot... Thanks for the article.
GeneralMy vote of 5memberAnaya Upadhyay12 Sep '12 - 22:12 
This article helped me a lot to do my thing with messageboxes.
BugDemo Crashesmemberbrother.gabriel12 Sep '12 - 16:44 
Salvete! I tried this interesting project. I ran the demo and got it to crash in my very first session, within just a minute or two.
Here is what I did:
 
I set the icon to Info.
Set Buttons to OK
Help to None
LongText to Checked
Deltares version to Checked
Font to Monaco
 
enable the timeout in deltares mode.
Click the Try button.
Whilst the countdown is proceeding, click the close box.
After playing around with this, I got an exception. I pushed continue, but the timer didn't work any more.
 
Also, in the AbortRetryIgnore in non deltares mode, the close box is grayed out, but it works right in YesNoCancel.
If you enabled deltares mode with a timeout, the close box is not grayed out, but it doesn't work, either, that is, it doesn't close the box.
 
I haven't looked at the source, just the demo.
Thank you for the nice project.
AnswerRe: Demo CrashesmemberAlbersC13 Sep '12 - 20:51 
Hello,
Thanks indeed for the nice component. Quite useful.
Just a small remark about the demo. I had an exception as well and the problem was in the module Dashboard.cs (see line 128..160).
 
What I did:
Checked Deltares version
Set Opacity to 0.6
Click button Try
 
In that case I got a NullReferenceException.
 
The changes that I made to correct this were very minor and you can see the code snippet below.
 
// Deltares extra options:
if ((buttonFont.Font != this.Font) ||
    (labelBackColour.BackColor != System.Drawing.SystemColors.Control) ||
    (labelForeColour.ForeColor != System.Drawing.SystemColors.ControlText) ||
    (checkBoxTimeOut.Checked) ||
    (checkBoxUseCheckbox.Checked) ||
    (checkBoxCenter.Checked) ||
    ((double)(numericUpDownOpacity.Value) != 1.0))
{
    if (extras == null) extras = new MessageBoxExtras();
    // Apply extra's that differ from default:
    if (buttonFont.Font           != this.Font)                               extras.Font      = buttonFont.Font;
    if (labelBackColour.BackColor != System.Drawing.SystemColors.Control)     extras.BackColor = labelBackColour.BackColor;
    if (labelForeColour.ForeColor != System.Drawing.SystemColors.ControlText) extras.ForeColor = labelForeColour.ForeColor;
    if (checkBoxCenter.Checked)                                               extras.CenterOwner = true;
    if (checkBoxUseCheckbox.Checked)
    {
        extras.CheckBox = new CheckBox();
        extras.CheckBox.Text = "Do &not show again";
        extras.CheckBox.Checked = true;
    }
    if (checkBoxTimeOut.Checked)
    {
        ProgressBarEx p = new ProgressBarEx();
        p.Text = null;
        p.Maximum = (int)(1000 * numericUpDownTimeOut.Value);
        p.Height  = 10;
        extras.ProgressBar = p;
 
    }
                // Check Opacity here otherwise object extras could be is null and a NullReferenceException is raised.
    if ((double)(numericUpDownOpacity.Value) != 1.0) extras.Opacity = (double)(numericUpDownOpacity.Value);}
 
            }
            // Moved up into the "Deltares extra options:" block.
            // if ((double)(numericUpDownOpacity.Value) != 1.0) extras.Opacity = (double)(numericUpDownOpacity.Value);

GeneralRe: Demo CrashesmemberAndriks7 Oct '12 - 19:48 
Albers, you're so right. stupid I didn't catch that one. Frown | :(
Andriks
 
A question that sometimes drives me hazy: am I or are the others crazy? | Albert Einstein

GeneralCoolmemberZac Greve12 Sep '12 - 14:11 
Nice Job! I know I will use this somewhere!
I think computer viruses should count as life. I think it says something about human nature that the only form of life we have created so far is purely destructive. We've created life in our own image.
Stephen Hawking

QuestionLet me get this straight...memberVitaly Tomilov6 Sep '12 - 12:12 
So, you wrote this library that emulates the standard message box for the sole purpose of being able to set the language right in the box when in runs under Windows XP?
 
Is this not a whole new shine on the word overkill?
Let's agree to disagree!
Boris the animal Just Boris.

AnswerRe: Let me get this straight...memberAndriks6 Sep '12 - 21:07 
Yes, you're absolutely right. That's why I waited so long before doing it... Wink | ;)
Andriks
 
A question that sometimes drives me hazy: am I or are the others crazy? | Albert Einstein

QuestionMy 5mvpMehdi Gholam6 Sep '12 - 3:08 
Great start for your first article, my 5.
Its the man, not the machine - Chuck Yeager
If at first you don't succeed... get a better publicist
If the final destination is death, then we should enjoy every second of the journey.

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

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 8 Oct 2012
Article Copyright 2012 by Andriks
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid