Lokalisierung zur Laufzeit
Frank Dzaebel, erstellt am: 28.3.2006, zuletzt geändert:  20.05.2008
Kategorie: Windows Forms, .NET-Version: (1.1)/2.0, [Download]

Es wird eine Möglichkeit gezeigt, wie man eine Windows Form zur Laufzeit lokalisiert darstellen kann und die Sprache umschalten kann.

   
Siehe auch die Möglichkeit aus Bernd Marquardts Webcast hier.

using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Globalization;
using System.Drawing;
using System.Threading;

namespace Lokalisierung_ZurLauf
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
    }

    private void btnSprache_Click(object sender,EventArgs e)
    {
      if (Thread.CurrentThread.CurrentCulture.Name == "en-US") 
        SwitchLanguage("");
      else SwitchLanguage("en-US");
      MessageBox.Show(DateTime.Now.ToLongDateString());
    }

    private void SwitchLanguage(string culture)
    {
      CultureInfo cInfo = new CultureInfo(culture);
      ComponentResourceManager resManager = new ComponentResourceManager(this.GetType());
      Point old_location = this.Location;
      resManager.ApplyResources(this,"$this",cInfo);
      this.Location = old_location; //nur für .NET 1.1 nötig

      //Controls.Clear(); // <<=== Sauberer, aber ggf. mehr Flackern (es sind dann keine Apply* Methoden mehr nötig)
      ApplyRessourcesAllControls(this,resManager,cInfo); // <<=== wenn "Controls.Clear" aktiviert, dann auskommentieren.

      Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
      Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);

      //InitializeComponent(); // <<=== wenn "Controls.Clear" aktiviert, dann hier auch.
      Form1_Load(this, EventArgs.Empty);
    }

    private void ApplyRessourcesAllControls(Control control,
      ComponentResourceManager resManager,CultureInfo cInfo)
    {
      foreach (Control ctl in ((Control)control).Controls)
      {
        if (ctl.Controls.Count > 0) ApplyRessourcesAllControls(ctl,resManager,cInfo);
        resManager.ApplyResources(ctl,ctl.Name,cInfo); // folgendes nur für .NET 2.0
        if (ctl is ToolStrip) ApplyRessourcesAllToolStrips((ToolStrip)ctl,resManager,cInfo);
      }
      foreach (Component cmp in this.components.Components) // z.B. ContextMenu
        if (cmp is ToolStrip) 
          ApplyRessourcesAllToolStrips((ToolStrip)cmp, resManager, cInfo);
    }

    private void ApplyRessourcesAllToolStrips(ToolStrip ts,ComponentResourceManager resManager,CultureInfo cInfo)
    {
      foreach (ToolStripItem tsi in ts.Items)
      {
        ToolStripDropDownItem tdi = tsi as ToolStripDropDownItem;
        if (tdi != null) ApplyAllToolStripItems(tdi,resManager,cInfo);
        ToolStripComboBox tdc = tsi as ToolStripComboBox;
        if (tdc != null) ApplyAllToolStripItems(tdc, resManager, cInfo);
        resManager.ApplyResources(tsi, tsi.Name, cInfo);
      }
      resManager.ApplyResources(ts,ts.Name,cInfo);
    }

    private void ApplyAllToolStripItems(ToolStripItem tsi,ComponentResourceManager resManager,CultureInfo cInfo)
    {
      ToolStripDropDownItem tdi = tsi as ToolStripDropDownItem;
      if (tdi != null)
      {
        foreach (ToolStripItem tsi2 in tdi.DropDownItems)
        {
          ToolStripDropDownItem tdi2 = tsi2 as ToolStripDropDownItem;
          if (tdi2 != null && tdi2.DropDownItems.Count > 0) 
            ApplyAllToolStripItems(tdi2,resManager,cInfo);
          resManager.ApplyResources(tsi2,tsi2.Name,cInfo);
        }
      }
      ToolStripComboBox tdc = tsi as ToolStripComboBox;
      if (tdc != null)
        for (int i = 0; i < tdc.Items.Count; i++)
        {
          tdc.Items[i] = resManager.GetString(tdc.Name+".Items"+
            ((i == 0) ? "" : i.ToString()), cInfo);
        }
      resManager.ApplyResources(tsi, tsi.Name, cInfo);
    }

    private void Form1_Load(object sender, EventArgs e)
    {
      // Beispiel für manuellen Lokalisierungs-Aufruf
      lblBegrüssung.Text = Properties.Resources.Begrüssung;
    }

  }
}
Möglichkeit aus Bernd Marquardts Webcast (Minute 50:00): private void SetLanguage(string strLang) { Point pt = this.Location; Size sz = this.Size; Thread.CurrentThread.CurrentUICulture = new CultureInfo(strLang); this.Controls.Clear();
this.Events.Dispose(); //dadurch würde die App nicht sauber schliessen.
InitializeComponent(); this.Size = sz; this.Location = pt; }

Da es bei obiger Lösung Probleme gibt, weil bestimmte interne Ereignisse durch Events.Dispose() ebenfalls gelöscht werden, kann man ggf. mit Workarounds arbeiten, die private Variablen verwenden (minimal getestet auch auf Visual Studio 9.0 Beta1) :
private void btnSprache_Click(object sender, EventArgs e)
{
  SetLanguage("en-US");
  MessageBox.Show(DateTime.Now.ToLongDateString());
}

private void SetLanguage(string language)
{
  Point pt = this.Location; Size sz = this.Size;
  Thread.CurrentThread.CurrentUICulture = new CultureInfo(language);
  Thread.CurrentThread.CurrentCulture = new CultureInfo(language);
  this.Controls.Clear();
  RemoveAllHandlersExcept(this.Events, "OnMainFormDestroy");
  InitializeComponent();
  this.Size = sz; this.Location = pt;
}

const BindingFlags bf = BindingFlags.Instance | BindingFlags.NonPublic;

void RemoveAllHandlersExcept(EventHandlerList ehl, string handlerName)
{
  object head = FieldValue(ehl, "head");
  object next = FieldValue(head, "next");
  if (next != null) RemoveAllExcept(ehl, next, handlerName);
}

private void RemoveAllExcept(EventHandlerList ehl,
  object listEntry, string handlerName)
{
  object next = FieldValue(listEntry, "next");
  if (next != null) RemoveAllExcept(ehl, next, handlerName);
  Delegate handler = (Delegate)FieldValue(listEntry, "handler");
  if (handler.Method.Name == handlerName) return;
  object key = FieldValue(listEntry, "key");
  ehl.RemoveHandler(key, handler);
}

FieldInfo fi;
object FieldValue(object source, string fieldName)
{
  fi = source.GetType().GetField(fieldName, bf);
  return fi.GetValue(source);
}