Skip to main content
Email Password   helpLost your password?

Contents

Introduction

A month ago, I started localizing a Windows application I was developing. There are tons of information regarding this topic on MSDN, so after a while, I could run my application with an English or German user interface. Unfortunately, the desired UI culture had to be assigned at the very beginning of my program. But, I was looking for a way to also change the culture of the user interface at runtime, e.g., after clicking on a specific menu item. After an unsuccessful search on MSDN and the rest of the internet, I decided to do it on my own.

Localization and assignment of a UI culture

At the beginning, I took a close look at how localization works when starting my application. And, it works pretty simple. Inside the form designer generated InitializeComponent method, a ComponentResourceManager instance is created, which provides access to (localized) resources considering the culture of the current thread, and a fallback mechanism if no resources are available for this culture (more details can be found on MSDN). Following this, its ApplyResources method gets called for all the UI controls located on the form as well as the Form instance itself to assign the corresponding resources. The calls for the UI controls thereby have a fixed pattern, with the control itself as the first and its name as the second parameter. The following example lines are copied from the InitializeComponent method of the demo application:

private void InitializeComponent()
{
    System.ComponentModel.ComponentResourceManager resources = 
        new System.ComponentModel.ComponentResourceManager(typeof(Form1));
    ...
    resources.ApplyResources(this.label1, "label1");
    ...
    resources.ApplyResources(this, "$this");
    ...
}

To start the application with a specific UI culture for which resources are available, we have to add the following line before the InitializeComponent method gets called (the example applies to German culture).

Thread.CurrentThread.CurrentUICulture = new CultureInfo("de");

Developing the change functionality

To switch between cultures, I created two menu items for English and German, and defined event handlers for their Click events. Inside of these, I call a method named ApplyCulture, which should encapsulate the change of the UI culture and pass an appropriate CultureInfo object. Here, I'm going to describe the development of the ApplyCulture method.

At first, the method assigns the passed CultureInfo object to the CurrentUICulture property of the current thread, because, as described above, this is used by ComponentResourceManager to determine which resources should be used.

In a first attempt, I simply cleared the ControlCollection of my form, and afterwards, called its InitializeComponent method, so the user interface is constructed again considering the changed UI culture. This solution successfully changed the culture of the user interface, but I regarded it as being pretty rude. Also, there were some drawbacks, e.g., changes to the Enabled property can get lost, or the initial size of a form gets reassigned.

private void ApplyCulture(CultureInfo culture)
{
    Thread.CurrentThread.CurrentUICulture = culture;
    
    this.Controls.Clear();
    this.InitializeComponent();
}

To be more elegant, I decided to only call the ComponentResourceManager.ApplyResources method for all the UI controls, as done inside the InitializeComponent method. Because these calls have a fixed pattern and the Visual Studio designer defines a field for all of them, this can easily be done via Reflection. Information about all non-public, non-inherited instance fields of the form are retrieved and filtered for the UI controls (fields whose type is derived from Component). Finally, the ApplyResources method of a created ComponentResourceManager is called for the filtered UI controls, and the field itself as well as its name are passed as parameters. Although this second solution changed the culture of the user interface more elegantly, there were still the same drawbacks, like the possible loss of changes to the Enabled property.

private void ApplyCulture(CultureInfo culture)
{
    Thread.CurrentThread.CurrentUICulture = culture;
    
    ComponentResourceManager resources = 
        new ComponentResourceManager(this.GetType());
    FieldInfo[] fieldInfos = this.GetType().GetFields(BindingFlags.Instance |
        BindingFlags.DeclaredOnly | BindingFlags.NonPublic);

    for (int index = 0; index < fieldInfos.Length; index++)
    {    
        if (fieldInfos[index].FieldType.IsSubclassOf(typeof(Component)))
        {
            resources.ApplyResources(fieldInfos[index].GetValue(this), 
                                     fieldInfos[index].Name);
        }
    }
}

To avoid these drawbacks, the final solution replaced the calls to the ComponentResourceManager.ApplyResources method. It only loads the localized text of the UI controls by calling the ComponentResourceManager.GetString method and passing a string with the format "[name of the UI control].Text" (e.g., "label1.Text"). If the returned string isn't null, it's assigned to the Text property of the UI control. Because this solution requires the existence of a Text property, the reflected fields of the form are now filtered by reflecting if they have such a property. If the form and the contained UI controls auto-size, the change of the user interface culture can cause a nervous resizing, because each assignment of localized text to a UI control can change the layout. To avoid this effect, the layout logic is halted for the form and all its fields that are derived from the Control class before changing the culture. Afterwards, the layout logic is resumed and layout changes are performed. Thereby, again, reflection is used to call the methods Control.SuspendLayout and Control.ResumeLayout on all the fields that are derived from Control.

private void ApplyCulture(CultureInfo culture)
{
    // Applies culture to current Thread.

    Thread.CurrentThread.CurrentUICulture = culture;

    // Create a resource manager for this Form
    // and determine its fields via reflection.

    ComponentResourceManager resources = new ComponentResourceManager(this.GetType());
    FieldInfo[] fieldInfos = this.GetType().GetFields(BindingFlags.Instance |
        BindingFlags.DeclaredOnly | BindingFlags.NonPublic);
    
    // Call SuspendLayout for Form and all fields derived from Control, so assignment of 
    // localized text doesn't change layout immediately.

    this.SuspendLayout();
    for (int index = 0; index < fieldInfos.Length; index++)
    {    
        if (fieldInfos[index].FieldType.IsSubclassOf(typeof(Control)))
        {
            fieldInfos[index].FieldType.InvokeMember("SuspendLayout", 
                BindingFlags.InvokeMethod, null, 
                fieldInfos[index].GetValue(this), null);
        }
    }
    
    // If available, assign localized text to Form and fields with Text property.

    String text = resources.GetString("$this.Text");
    if (text != null)
        this.Text = text;
    for (int index = 0; index < fieldInfos.Length; index++)
    {
        if (fieldInfos[index].FieldType.GetProperty("Text", typeof(String)) != null)
        {
            text = resources.GetString(fieldInfos[index].Name + ".Text");
            if (text != null)
            {   
                fieldInfos[index].FieldType.InvokeMember("Text",
                    BindingFlags.SetProperty, null,
                    fieldInfos[index].GetValue(this), new object[] { text });
            }
        }
    }
    
    // Call ResumeLayout for Form and all fields
    // derived from Control to resume layout logic.
    // Call PerformLayout, so layout changes due
    // to assignment of localized text are performed.

    for (int index = 0; index < fieldInfos.Length; index++)
    {
        if (fieldInfos[index].FieldType.IsSubclassOf(typeof(Control)))
        {    
            fieldInfos[index].FieldType.InvokeMember("ResumeLayout",
                    BindingFlags.InvokeMethod, null,
                    fieldInfos[index].GetValue(this), new object[] { false });
        }
    }
    this.ResumeLayout(false);
    this.PerformLayout();
}

Developing the UICultureChanger component

The feedback on the first version of this article showed me that I'm not the only one who needs the presented functionality, but also that it's not yet sufficient for everyone. There are needs to change the UI culture of multiple forms, and to apply not only localized text but also sizes or locations. So, I decided to enhance the change functionality and put it into a component to improve its usage.

Demo application

Demo application

The demo application is a simple MDI application that shows the capabilities of the UICultureChanger component. The parent form hosts an instance of the component, and allows you to customize it at runtime through the UICultureChanger menu entry. The text of menu items is localized, and the parent form contains a HelpProvider component that opens a localized topic, if no child form is open and F1 gets pressed. Furthermore, the RightToLeft property is localized, and the parent form is sizable, so you can see the effects of the component's PreserveFormSize property.

The child forms contain some labels and buttons whose text, sizes, locations, and/or tooltips are localized as well as the necessary ToolTip component. To show the effects of the PreserveFormLocation property, the child forms have a manual start position, which gets reapplied if PreserveFormLocation is false. Furthermore, the RightToLeft and also the RightToLeftLayout properties are localized.

Using the component

  1. Localize your application, which is described by MSDN. To have UI controls with dynamic content that isn't affected by changes to the user interface culture, delete their text in the form designer. (The demo application, for example, contains a label that shows the application startup time and doesn't get changed.)
  2. Add the UICultureChanger component to your project using one of the following possibilities:
  3. In the form designer, drag the UICultureChanger component from the Toolbox over to your main form and customize the component.
  4. Provide a way to choose between different cultures (e.g., with menu items, as done in the demo application), and call the ApplyCulture method on the generated member variable of the UICultureChanger component.
  5. If you want to change the UI culture of multiple forms (e.g., in an MDI application), for each form, call the AddForm method on the generated member variable of the UICultureChanger component.

Version history

2.4
  • Application of localized Size and Location values considers Anchor settings.
2.3
  • Fixed bug that the text of a RichTextBox was changed although its Text property is set to an empty string in the form designer.
2.2
  • Added support for application of localized ToolTipText values.
2.1
  • Added support for application of localized RightToLeft and RightToLeftLayout values.
  • Added option to compile for .NET Framework versions prior 2.0.
2.0
  • Implemented as a component.
  • Supports changing the UI culture of multiple forms.
  • Supports application of localized tooltips, help contents, Text, Size, and Location values.
1.0
  • Initial release.

UICultureChanger is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralA couple problems and their solutions, and an unsolved problem with TreeView, and a suggestion Pin
mjmeans
18:53 16 Mar '09  
GeneralGreat tip Pin
Mohsen Afshin
22:14 1 Mar '09  
QuestionProblems with the DLL Pin
MaxxTc
19:34 6 Jul '08  
AnswerRe: Problems with the DLL Pin
Stefan Troschuetz
8:59 25 Jul '08  
GeneralUserControl support Pin
eminsenay
23:38 26 Feb '08  
GeneralUsercontrols Pin
Member 4316788
5:33 10 Jan '08  
GeneralDoesn't update DataGridView-Control Pin
intripoon
3:36 12 Dec '06  
GeneralRe: Doesn't update DataGridView-Control Pin
Stefan Troschütz
10:39 12 Dec '06  
GeneralExcellent Component Pin
Peter Huber SG
23:23 28 Nov '06  
GeneralFurther improvements Pin
Phil J Pearson
12:03 5 Sep '06  
GeneralRe: Further improvements Pin
Stefan Troschütz
22:48 5 Sep '06  
GeneralRe: Further improvements Pin
Yumashin Alex
20:41 22 Oct '06  
GeneralRe: Further improvements Pin
Stefan Troschütz
22:45 24 Oct '06  
GeneralPossible null reference problem fixed Pin
Phil J Pearson
3:36 5 Sep '06  
GeneralRe: Possible null reference problem fixed Pin
Stefan Troschütz
22:41 5 Sep '06  
GeneralTitlebar Revered Bug? Pin
Tonster101
22:16 20 Aug '06  
GeneralRe: Titlebar Revered Bug? Pin
Stefan Troschütz
23:40 26 Aug '06  
GeneralProblem with Anchor and Dock properties Pin
Yumashin Alex
1:56 7 Aug '06  
GeneralRe: Problem with Anchor and Dock properties Pin
Stefan Troschütz
6:38 7 Aug '06  
GeneralRe: Problem with Anchor and Dock properties Pin
Yumashin Alex
23:35 7 Aug '06  
GeneralAnd one thing more... Pin
Yumashin Alex
23:37 7 Aug '06  
GeneralRe: Problem with Anchor and Dock properties Pin
Stefan Troschütz
5:25 20 Aug '06  
GeneralAny property, but different resx files Pin
Ajek
2:44 4 Jul '06  
QuestionProblem with RichTextBox Pin
Yumashin Alex
1:57 27 Jun '06  
AnswerRe: Problem with RichTextBox Pin
Stefan Troschütz
7:17 28 Jun '06  


Last Updated 20 Aug 2006 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2009