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

Localizing System MessageBox

By , 2 May 2007
 
Screenshot - Article.gif

Introduction

In our days, most applications must have a certain degree of localizability. More than often, I find myself in need for a component capable of showing simple messages, as the system MessageBox does, but capable of localization. There are tons of samples on how such a component can be implemented, but such a task is somewhat bigger than its purpose. On the other hand, you may already use components that use the system message box, which you cannot or just don't want to change. So, I implemented a static class named MessageBoxManager that is capable of setting the text of the system MessageBox buttons for all message boxes shown via the System.Windows.Forms.MessageBox.Show() method call.

Using the Code

This is really simple. Just add the MessageBoxManager class to your project, or include a reference to MessageBoxManager.dll, and follow the sample below:

[STAThread]
static void Main()
{
    MessageBoxManager.OK = "Alright";
    MessageBoxManager.Cancel = "Noway";
    MessageBoxManager.Register();
    MessageBox.Show("This is a message...","Test",MessageBoxButtons.OKCancel);
    MessageBoxManager.Unregister();
}

or

[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    System.Threading.Thread.CurrentThread.CurrentUICulture = 
				new System.Globalization.CultureInfo("ro");

    //Set button text from resources
    MessageBoxManager.OK = LocalResource.OK;
    MessageBoxManager.Cancel = LocalResource.Cancel;
    MessageBoxManager.Retry = LocalResource.Retry;
    MessageBoxManager.Ignore = LocalResource.Ignore;
    MessageBoxManager.Abort = LocalResource.Abort;
    MessageBoxManager.Yes = LocalResource.Yes;
    MessageBoxManager.No = LocalResource.No;

    //Register manager
    MessageBoxManager.Register();

    Application.Run(new Form1());
}

The MessageBoxManager implementation uses windows hooks internally. Whenever the application shows a Windows message box, MessageBoxManager changes its buttons' text accordingly.

The properties of the MessageBoxManager can be changed at any time. If you wish to change them, you don't have to unregister.

When multiple GUI threads are used, it is important to know that MessageBoxManager.Register() and MessageBoxManager.Unregister() work on a thread basis. Therefore the MessageBoxManager.Register() must be called for each GUI thread individually. Please note that buttons text cannot be set on a thread basis, these stand for all running threads.

How It Works

MessageBoxManager basically uses the Win32 API. Please see the code, it is pretty straightforward.

To access Win32 API from .NET, you need to make use of SecurityPermission attribute:

[assembly: SecurityPermission(SecurityAction.RequestMinimum, UnmanagedCode = true)]

You will need to declare the API functions you are going to use as follows:

[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int maxLength);

Now, let's see the Register() method:

private static HookProc hookProc;
private static EnumChildProc enumProc;
[ThreadStatic]
private static IntPtr hHook;

/// <summary>
/// Enables MessageBoxManager functionality
/// </summary>
/// <remarks>
/// MessageBoxManager functionality is enabled on current thread only.
/// Each thread that needs MessageBoxManager functionality has to call this method.
/// </remarks>
public static void Register()
{
    if (hHook != IntPtr.Zero)
        throw new NotSupportedException("One hook per thread allowed.");
    hHook = SetWindowsHookEx(WH_CALLWNDPROCRET, hookProc, 
		IntPtr.Zero, AppDomain.GetCurrentThreadId());
}

Register() method does only one thing, it registers a windows hook for intercepting windows messages after they have been processed by the window procedure. Please note the ThreadStatic attribute for the hook handle. It ensures that the variable value is unique for each thread. hHook will store for each thread a different handle.

The intercepting callback is the MessageBoxHookProc method.

private static IntPtr MessageBoxHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode < 0)
        return CallNextHookEx(hHook, nCode, wParam, lParam);
    CWPRETSTRUCT msg = (CWPRETSTRUCT)Marshal.PtrToStructure
				(lParam, typeof(CWPRETSTRUCT));
    IntPtr hook = hHook;
    if (msg.message == WM_INITDIALOG)
    {
        int nLength = GetWindowTextLength(msg.hwnd);
        StringBuilder className = new StringBuilder(10);
        GetClassName(msg.hwnd, className, className.Capacity);
        if (className.ToString() == "#32770")
        {
            EnumChildWindows(msg.hwnd, enumProc, IntPtr.Zero);
        }
    }
    return CallNextHookEx(hook, nCode, wParam, lParam);
}

We are interested only in WM_INITDIALOG messages and only for windows of class "#32770" which is a special window class that messagebox window belongs to.

Once we intercepted the right message, we can start processing. This means enumerating all child windows, locating buttons and changing button text as we need.

private static bool MessageBoxEnumProc(IntPtr hWnd, IntPtr lParam)
{
    StringBuilder className = new StringBuilder(10);
    GetClassName(hWnd, className, className.Capacity);
    if (className.ToString() == "Button")
    {
        int ctlId = GetDlgCtrlID(hWnd);
        switch (ctlId)
        {
        case MBOK:
            SetWindowText(hWnd, OK);
            break;
        case MBCancel:
            SetWindowText(hWnd, Cancel);
            break;
        case MBAbort:
            SetWindowText(hWnd, Abort);
            break;
        case MBRetry:
            SetWindowText(hWnd, Retry);
            break;
        case MBIgnore:
            SetWindowText(hWnd, Ignore);
            break;
        case MBYes:
            SetWindowText(hWnd, Yes);
            break;
        case MBNo:
            SetWindowText(hWnd, No);
            break;
        }
    }
    return true;
}

We identify the buttons based on their dialog control ID. OK is 1, Cancel is 2, etc. This can be easily found using the Spy++ utility that came with Visual Studio.

Is There Anything More?

Yes, actually there is. This "#32770" window class is not used only for MessageBox windows but also for the system open file window, print window, and more. If you need to use more system windows in your application but you have no better option to localize them than the one described in this article, you may extend the MessageBoxManager class to handle these windows too. All you need to do is to identify the dialog controls ID for the window labels using Spy++ utility, and add them in the MessageBoxEnumProc switch statement. You will find it very convenient that the dialog IDs for the same control in different windows are identical and unique. Setting the OK button text for the message box covers all ok buttons for all system windows.

Why Not Let OS Handle It by Setting the Proper Culture?

This works only if the desired language is installed on the system. Some people in some countries cannot, or prefer not to use the language packs provided by Microsoft. According to Microsoft, they only cover 80% of the UI. Also, if the native language of your users is not widely spread, the chance that the rest of the applications they use to be localized for their language is quite low. For these applications, they would see mixes such as texts in English but buttons in their own language, which may be disgracefully enough to avoid installing the language pack.

However, if there exists a language pack for your customers' native language, although for practical reasons I believe it is not always indicated to have it installed, I believe the text translation should be taken from there. This is done so that your application will look similar to the rest, if by chance the pack is or will be installed on the user's machine.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Alex C. Duma
Web Developer
Romania Romania
Member
No Biography provided

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   
QuestionCan I use this code in my project ???memberserkan_serkan25 Mar '13 - 23:10 
Can I use this code in my project?
 
Thank you very much. You open my mind.
AnswerRe: Can I use this code in my project ???memberAlex C. Duma26 Mar '13 - 0:41 
Sure, this article and the source code is free.
Alex C. D.

GeneralRe: Can I use this code in my project ???memberserkan_serkan26 Mar '13 - 3:01 
i made my messagebox yesterday. Big Grin | :-D
but i had some problem. if masaj is long. i should make organize that mesaj.
But i found your tutorial. i finished that. thank you very much.
 
if you had a problem, i can try to solve Big Grin | :-D
Generalthank youmemberserkan_serkan25 Mar '13 - 23:01 
excellent tutorial. Thank you very much.
GeneralMy vote of 5membertibi2252 Dec '12 - 0:22 
Thanks a lot for this. It's not only great & easy usable solution but also a great description about what you did. Wink | ;)
GeneralMy vote of 5membermr.mabo24 Oct '12 - 4:12 
Really helpful, excellent tutorial.
QuestionError in MessageboxManagermemberSaridakis Manolis13 Oct '12 - 22:57 
Hello,

The problem is, when I run a method from other form (example: From form1 I call form2.mymethod) , it show me error in MessageBoxManager.cs
in:
hHook = SetWindowsHookEx(WH_CALLWNDPROCRET, hookProc, IntPtr.Zero, AppDomain.GetCurrentThreadId());

How can I solve this ?

Best regards
Manolis Saridakis
GeneralMy vote of 5memberalaalah1 Sep '12 - 10:53 
Exactly what i want and it's a professional article
GeneralMy vote of 5memberJasonMSA1 Feb '11 - 4:19 
Just apply the MessageBoxManager Class and works straight away, saves me lot of work! Want to even give u a kiss if I could! haha!!! BIG thank you!!!
QuestionModifying class to work with Syncfusion MessageBoxAdv?membermaria stein11 May '10 - 1:03 
Hi!
 
Thank you! This is working great. What would I have to modify to make it work with Syncfusion MessageBoxAdv though?
 
/M
AnswerRe: Modifying class to work with Syncfusion MessageBoxAdv?memberHuyh7 Feb '12 - 22:28 
I have encountered the same issue about MessageBoxAdv. Any solution?
Jokethank you very muchmemberdoanhathanh4 Dec '09 - 7:46 
good job!
NewsVery important Notememberamostafa847 Nov '09 - 1:56 
Hello
 
In AssemblyInfo.cs file you can find line like this
[assembly: NeutralResourcesLanguageAttribute("ar-SA")]
it will stop working MessageManager..
eliminate it..
 
Good Luck ..
 
Ahmed Mostafa ElDeeb
Solution Developer
 
email: amostafa84@hotmail.com

QuestionHow to size buttons to text?member_dog9 Jul '09 - 11:08 
Hi,
 
I'd like to compliment you on the usefulness and simplicity of the MessageBoxManager class. I like the fact that it uses the standard MessageBox instead of a custom Form, which many of the alternative postings I've seen do, so it can use standard MessageBox options, like overriding Right-To-Left, depending on the language.
 
I have found a couple of issues, and I'd like to get your opinion on these:
 

1. Is there a way to resize the buttons to the text?
 
I am using this to replace Yes/No/Cancel button text, for example:
 
MessageBoxManager.Yes = "Open for editing";
MessageBoxManager.No = "Open in read-only mode";
 
The button size is not sized to the text. Is there a way to autosize the overriden window?
 

2. Is there a way to have MessageBoxManager.Unregister() reset default button text? I have found I am not able to subsequently reuse the standard MessageBox buttons without providing text for previously overridden text. For example, if I override the Cancel button for one MessageBox text as:
 
MessageBoxManager.Cancel = "Don't change anything";
 
I can use it for that MessageBox. And after MessageBoxManager.Unregister() I can use the standard MessageBox, this is all fine. But in the same application if I want to use MessageBoxManager with another MessageBox that does NOT override the Cancel button, I still see the overridden Cancel button text, but in this new MessageBox, I would like to use the default Cancel button text provided by the OS.
 

Thanks!
Denny
AnswerRe: How to size buttons to text?memberdumaal9 Jul '09 - 23:19 
Thanks Denny for your message. It has been a long time since I have written this article and it is nice to see it is still being of interest.
In regard to your questions
 
1. Buttons and the Messagebox window can be resized and an autosize feature can be implemented by using SetWindowPos Win API call, similar to the use of SetWindowText.
...something like:
private const uint SWP_NOZORDER = 4;
[DllImport("user32.dll", EntryPoint = "SetWindowPos", CharSet = CharSet.Unicode)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
...
private static bool MessageBoxEnumProc(IntPtr hWnd, IntPtr lParam)
{
...
case MBOK:
SetWindowText(hWnd, OK);
SetWindowPos(hWnd, IntPtr.Zero, calculatedX, calculatedY, calculatedWidth, calculatedHeight, SWP_NOZORDER);
break;
...
Sizes can be calculated automatically based on window text and font settings. ..though I am not considering to implement this feature since it is a bit further than the demonstration I was intended.
 
2. MessageBoxManager works at the application level localizing text for all calls related to the system MessageBox. Obviously the system MessageBox API has no extra parameters to allow us specifying which call should be localized and which not, so whenever one needs a different localization he needs to place an extra call to MessageBoxManager for setting the new button text.
It is not required to use the Unregister() method in order to set new text for buttons. The implementation can indeed be changed so texts reset after each call as you suggest but then it will still be necessary an extra call to MessageBoxManager before each MessageBox.Show(...) call to instruct which text the MessageBox will use original or custom.
 
Best regards,
Alex
Generaldownload the source codememberronitrom5 May '09 - 21:15 
I am not able to download the code.
Please help.
 
Ronit Rom
ronitr@systematics.co.il
GeneralRegarding licence termsmemberdeepa.abhyankar5 Apr '09 - 19:50 
Hi Alex,
 
I went throught your article on Localizing System MessageBox.
Does the code of this article comes with any licence.
If yes can you give me licencing details?
 
Deepali
GeneralRe: Regarding licence termsmemberzipperle16 May '09 - 2:48 
Licencing details would be interesting for me too.
QuestionWhat changes I have to make to work in a Device Application?membersotero.mauricio2 Apr '09 - 8:27 
Hi Thanks a lot. But I am doing a Device Application for Windows CE and this code doesn't work.
 
Thanks,
Maurício
QuestionIs #32770 is constant in any windows os version?memberEliram Benitah16 Mar '09 - 22:42 
It works fine at XP.
Can i assumes that it will be work on other windows versions?
GeneralThank you very much!memberEliram Benitah16 Mar '09 - 10:02 
You did a very nice and helpfull work.
 
Thanks!Thumbs Up | :thumbsup:
GeneralThanks!memberLinoge-Fly4 Mar '09 - 0:37 
Thank you very much for your solution.
Simple and usage and great result, thanks!
GeneralSalut [modified]memberliviucatrina30 Jan '09 - 4:03 
Numele meu este Liviu Catrina si sunt programator. Metoda prezentata este foarte interesanta, felicitari! M-am amuzat citind post-urile de mai jos. Cum sa inteleaga ei ca noi avem nevoie de Windows si Office in engleza dar la aplicatii de contabilitate, salarii samd e bine sa apara utilizatorului 'Da', 'Nu', 'Abandon' in loc de 'Yes', 'No', 'Cancel'. Oricum eu am folosit alta metoda pentru MessageBox dezvoltata din modelul de aici http://www.codeproject.com/KB/miscctrl/CsMsgBoxTimeOut.aspx. Mi s-a parut comod sa inlocuiesc doar MessageBox cu MessageBoxEx. In cadrul functiei hook trec in revista butoanele si le modific textul, in plus are si timer.
 
modified on Friday, January 30, 2009 10:09 AM

QuestionMerci, have anyone extended version for open and print dialog ?memberbiloujmm15 Dec '08 - 6:50 
Thanks for this useful class
 
Is there anybody that have already extended the MessageBoxManager class to handle the system open file window, print windows too ?
AnswerRe: Merci, have anyone extended version for open and print dialog ?membercatduss15 Dec '08 - 7:30 
I've done the Open file Dialog, but not the print dialog. You can look at the message I posted when I replied to the "button save" issue on dec 12. The labels in the Open dialog are the same as in the save dialog box.

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 2 May 2007
Article Copyright 2007 by Alex C. Duma
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid