Click here to Skip to main content
15,861,367 members
Articles / Programming Languages / C#

Localizing System MessageBox

Rate me:
Please Sign up or sign in to vote.
4.81/5 (96 votes)
2 May 2007CPOL4 min read 341.4K   20.6K   108   84
A simple class that allows customizing system's MessageBox window buttons
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:

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

or

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

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

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

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

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

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

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

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


Written By
Software Developer (Senior)
Romania Romania
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
Motaz Alnuweiri8-Dec-19 8:10
Motaz Alnuweiri8-Dec-19 8:10 
QuestionSave button Pin
Member 117881393-May-19 23:37
Member 117881393-May-19 23:37 
AnswerRe: Save button Pin
NettaD1-Jun-20 1:38
NettaD1-Jun-20 1:38 
QuestionExcellent Coding Pin
TheSilverFox29-Nov-17 14:21
TheSilverFox29-Nov-17 14:21 
GeneralMy vote of 5 Pin
Casper Kalimero29-May-17 3:51
Casper Kalimero29-May-17 3:51 
QuestionDetecting closure using X at the top Pin
Kamran Kazemi7-May-17 22:11
Kamran Kazemi7-May-17 22:11 
QuestionCan I sign the MessageBoxManager.dll generated from your source code? Pin
Member 47699231-Mar-17 23:57
Member 47699231-Mar-17 23:57 
QuestionUnfortunately doesn't work on Windows CE 6.0 :( Pin
Member 125045695-May-16 2:38
Member 125045695-May-16 2:38 
PraiseRe: Unfortunately doesn't work on Windows CE 6.0 :( Pin
Rui Oliveira Pinheiro12-Feb-18 1:23
Rui Oliveira Pinheiro12-Feb-18 1:23 
AnswerRe: Unfortunately doesn't work on Windows CE 6.0 :( Pin
Rui Oliveira Pinheiro12-Feb-18 1:51
Rui Oliveira Pinheiro12-Feb-18 1:51 
AnswerRe: Unfortunately doesn't work on Windows CE 6.0 :( Pin
Rui Oliveira Pinheiro12-Feb-18 2:03
Rui Oliveira Pinheiro12-Feb-18 2:03 
SuggestionEnhancement Pin
Jeronymite12-Apr-16 22:04
Jeronymite12-Apr-16 22:04 
Praisegood guide Pin
nealee17-Feb-16 20:16
nealee17-Feb-16 20:16 
PraiseThank you Pin
demon4078-Nov-15 21:01
demon4078-Nov-15 21:01 
PraiseVote 5 Pin
@cromx30-Oct-15 14:06
@cromx30-Oct-15 14:06 
QuestionIs there a way to keep the original keyboard shortcuts? Pin
zohar@tbh.co.il30-Sep-14 2:22
zohar@tbh.co.il30-Sep-14 2:22 
AnswerRe: Is there a way to keep the original keyboard shortcuts? Pin
Alex C. Duma30-Sep-14 8:12
Alex C. Duma30-Sep-14 8:12 
GeneralRe: Is there a way to keep the original keyboard shortcuts? Pin
zohar@tbh.co.il30-Sep-14 19:58
zohar@tbh.co.il30-Sep-14 19:58 
GeneralRe: Is there a way to keep the original keyboard shortcuts? Pin
Alex C. Duma1-Oct-14 9:43
Alex C. Duma1-Oct-14 9:43 
QuestionWorks like a charm, but ... Pin
laus773-Sep-14 4:55
laus773-Sep-14 4:55 
QuestionNew labels for buttons not shown with MessageBoxOptions.DefaultDesktopOnly option Pin
Member 252452719-Aug-14 18:42
Member 252452719-Aug-14 18:42 
GeneralMy vote of 5 Pin
Member 105233047-Aug-14 0:01
Member 105233047-Aug-14 0:01 
GeneralThanks Pin
Member 109947645-Aug-14 21:58
Member 109947645-Aug-14 21:58 
GeneralReally nice Pin
Dycz22-Jul-14 4:09
professionalDycz22-Jul-14 4:09 
GeneralMy vote of 1 Pin
Nathan Phillip Brink15-Jul-14 5:12
Nathan Phillip Brink15-Jul-14 5:12 

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.