Geschrieben von: Christoph Wille
Kategorie: .NET Allgemein
This printed page brought to you by AlphaSierraPapa
Ein wenig geliebtes Thema unter ASP war das Formatieren von Zahlen passend zu länderspezifischen Einstellungen - meist schummelten sich die Programmierer darüber hinweg, mit dem Ergebnis, daß wenn man von einem deutschen Webserver auf einen englischen migrierte, nichts mehr korrekt ausgegeben wurde. Unter .NET gibt es keine Ausreden mehr, seine Applikationen von vorne herein global auszulegen: Internationalisierung hat seinen Schrecken verloren.
Wir beschäftigen uns heute mit der landesspezifisch korrekt formatierten Ausgabe von Zahlenwerten, Währungswerten und Datumswerten. Die vorgestellten Techniken sind aber auch auf andere Internationalisierungsprobleme anwendbar, so zB das kulturell korrekte vergleichen von Zeichenketten. Übrigens: die heute vorgestellten Beispiele sind in ASP.NET programmiert - die vorgestellten Techniken sind 1:1 auch in allen anderen .NET Applikationen einsetzbar.
Die einfachste Variante ist, die Eigenschaft CurrentCulture des Threads, auf dem der Code läuft, umzusetzen. Diese Variante hat einen großen Vorteil: es ist nur eine Zeile Code notwendig, und alle nachfolgenden Aufrufe bedienen sich ab dann der neuen Einstellungen aus dem CultureInfo Objekt. Der folgende Code aus FormatOne.aspx demonstriert die Vorgehensweise:
<%@ Page Language="C#" %> <%@ Import Namespace="System.Globalization" %> <%@ Import Namespace="System.Threading" %> <html> <head> <title>Simple Formatting</title> </head> <body> <% // Testwerte DateTime dtVal = DateTime.Now; double dVal = 37068.89; // Ausgabe auf Default-Culture Response.Write(dtVal.ToString() + "<br>"); Response.Write(dVal.ToString() + "<br><br>"); // Umstellen des Threads auf Deutsch CultureInfo ciCurrent = new CultureInfo("de-DE"); Thread.CurrentThread.CurrentCulture = ciCurrent; // Ausgabe in deutschem Format Response.Write(dtVal.ToString() + "<br>"); Response.Write(dVal.ToString() + "<br>"); %> </body> </html>
Um diese Technik einzusetzen, müssen Sie die Namespaces System.Globalization (für die CultureInfo Klasse) und System.Threading (für das Thread Objekt) einbinden. Zuerst gibt das Programm einen Zahlenwert und ein Datum nach den aktuellen Einstellungen aus, danach stellen wir die CultureInfo des Threads auf Deutsch/Deutschland um:
CultureInfo ciCurrent = new CultureInfo("de-DE"); Thread.CurrentThread.CurrentCulture = ciCurrent;
Damit laufen wir ab sofort mit deutschen Einstellungen, und obwohl ich nichts am Ausgabecode geändert habe, bekomme ich nun korrekt deutsch formatierte Werte (die zweiten zwei Zeilen):
7/3/2002 6:30:09 PM 37068.89 03.07.2002 18:30:09 37068,89
Wie man an den ersten beiden Zeilen sieht, ist mein Server auf US Englisch eingestellt. Das macht aber nun nichts mehr, da ich jederzeit die CultureInfo ändern kann. Jederzeit beliebig umschalten, je nach dem, welche Einstellungen man gerade braucht.
Hin und wieder ist das Umstellen des ganzen Threads auf eine andere CultureInfo nicht gewünscht (zB ein Chatserver der mehrsprachige Chaträume bedient). Hier will man sich darauf beschränken, nur die jeweilige Formatierungsoperation mit anderen Einstellungen durchzuführen. Dafür gibt es bei zB ToString oder String.Format Overloads, die ein Interface des Typs IFormatProvider annehmen, mit dem dann die Ausgabe kulturspezifisch gemacht wird.
Wie kommt man an ein solches Interface heran? Sehr einfach: die CultureInfo Klasse implementiert es. Die Probe aufs Exempel liefert FormatTwo.aspx:
<%@ Page Language="C#" %> <%@ Import Namespace="System.Globalization" %> <html> <head> <title>Simple Formatting, 2</title> </head> <body> <% // Testwerte DateTime dtVal = DateTime.Now; double dVal = 37068.89; CultureInfo ciGermany = new CultureInfo("de-DE"); Response.Write(dtVal.ToString(ciGermany) + "<br>"); Response.Write(dVal.ToString(ciGermany) + "<br><br>"); CultureInfo ciCdnFrench = new CultureInfo("fr-CA"); Response.Write(String.Format(ciCdnFrench, "{0} {1}", dtVal, dVal)); %> </body> </html>
Ich erspare mir einen Namespace-Import, und auch das zentrale umschalten der CurrentThread.CultureInfo. Ich muß nur das CultureInfo Objekt an die entsprechenden Methoden übergeben (ToString und Format). Nicht nur die Formatierung kann man auf diese Art und Weise bewerkstelligen, auch Stringvergleiche laufen nach diesem Schema ab.
Damit Sie ein kleines Tool zum Experimentieren haben, erstellen wir ein ASP.NET Formular, mit dem Sie die verschiedenen CultureInfo's austesten können. Fertig sieht das Tool so aus:
In der linken Listbox werden die neutralen Cultures dargestellt (CultureTypes.NeutralCultures). Die rechte Listbox wird nach Selektionsänderung links mit den Unterkategorien der neutralen Culture befüllt (CultureTypes.SpecificCultures). Wird dann eine spezielle Culture ausgewählt, zeigen wir unter den Listboxen Beispiele an, wie sie für diese CultureInfo typisch sind.
Als Tool zur Erstellung dieses Web Forms habe ich das ASP.NET Web Matrix Projekt verwendet. Der erste Schritt ist eine neue ASPX Datei anzulegen:
Danach fügen wir die linke Listbox ein (Drag and Drop), und geben dieser den Namen lbNeutralCultures. Diese müssen wir mit den neutralen Cultures befüllen, und dies passiert am besten im Load Event der Seite (siehe Screenshot - im Load Event doppelklicken):
Damit sind wir in der Codeansicht. Geben Sie folgenden Code im Load Event ein:
void Page_Load(Object sender, EventArgs e) { if (!Page.IsPostBack) { CultureInfo[] aCIs = CultureInfo.GetCultures(CultureTypes.NeutralCultures); for (int i=0; i < aCIs.Length; i++) { lbNeutralCultures.Items.Add(new ListItem(aCIs[i].EnglishName + " [" + aCIs[i].Name + "]", aCIs[i].Name)); } } }
Die statische Methode GetCultures liefert uns ein Array von CultureInfo Objekten. Die so erhaltenen Werte tragen wir in die Listbox ein. Der Value der Listbox wird für das neuerliche Erzeugen des CultureInfo Objekts verwendet, nämlich dann, wenn der User einen anderen Wert in der Listbox auswählt.
Die Auswahl eines neuen Wertes triggert das SelectedIndexChanged Event. Wählen Sie zuerst aber an, daß die Listbox ein AutoPostback auslöst, weil sonst passiert nämlich gar nichts! Der Code für das Event sieht so aus (lbSpecificCultures ist die zweite Listbox, lblInfo der Beispieltext der ausgegeben wird):
void lbNeutralCultures_SelectedIndexChanged(Object sender, EventArgs e) { lbSpecificCultures.Items.Clear(); lblInfo.Text = ""; CultureInfo ciCurrent = new CultureInfo(lbNeutralCultures.SelectedItem.Value); CultureInfo[] aCIs = CultureInfo.GetCultures(CultureTypes.SpecificCultures); for (int i=0; i < aCIs.Length; i++) { if (ciCurrent.Equals(aCIs[i].Parent)) { ListItem li = new ListItem(aCIs[i].EnglishName + " [" + aCIs[i].Name + "]", aCIs[i].Name)); lbSpecificCultures.Items.Add(li); } } }
Der etwas umständliche Teil hier ist, daß man für eine neutrale Culture sich nicht alle spezifischen Cultures auflisten lassen kann. Man muß sich alle spezifischen holen, und dann mit ciNeutral.Equals(ciSpecific.Parent) herausfinden, ob denn die spezifische Culture ein Kind der neutralen ist. Weniger cool, aber that's life.
Der letzte Schritt ist, die Beispiele nach Culture formatiert anzuzeigen. Dies passiert nachdem der User in der rechten Listbox die Auswahl geändert hat. Also auf der Listbox lbSpecificCultures AutoPostback auf True setzen, und das SelectedIndexChanged Event implementieren:
void lbSpecificCultures_SelectedIndexChanged(Object sender, EventArgs e) { StringBuilder strInfo = new StringBuilder(); CultureInfo ciCurrent = new CultureInfo(lbSpecificCultures.SelectedItem.Value); if (!ciCurrent.IsNeutralCulture) { Thread.CurrentThread.CurrentCulture = ciCurrent; strInfo.Append("Kurzes Datum: "); strInfo.Append(DateTime.Now.ToShortDateString()); strInfo.Append("<br>Langes Datum: "); strInfo.Append(DateTime.Now.ToLongDateString()); strInfo.Append("<br>Kurze Zeit: "); strInfo.Append(DateTime.Now.ToShortTimeString()); strInfo.Append("<br>Lange Zeit: "); strInfo.Append(DateTime.Now.ToLongTimeString()); strInfo.Append("<br>Zahl (35007.09): "); strInfo.Append((35007.09).ToString("n")); strInfo.Append("<br>Währung (1057.80): "); strInfo.Append((1057.80).ToString("c")); } lblInfo.Text = strInfo.ToString(); }
Damit hat man den Culture Browser fertig, und kann sich "live" die Unterschiede der einzelnen Cultures ansehen, in Bezug auf Datums-, Zahlen- und Währungswerte. So auf auch chinesisch (wenn man die Schriften installiert hat):
Das Formatieren von Werten in Bezug auf eine spezielle Culture ist mit .NET extrem vereinfacht worden, man muß keine Winkelzüge mehr machen. Und ein weiterer Vorteil: das Wissen um Internationalisierung muß man sich nur einmal aneignen, man kann es in allen .NET Anwendungen einsetzen.
This printed page brought to you by AlphaSierraPapa
Klicken Sie hier, um den Download zu starten.
http://www.aspheute.com/code/20020704.zip
@-Direktiven auf ASP Seiten
http:/www.aspheute.com/artikel/20000405.htm
Das ASP.NET Web Matrix Projekt
http:/www.aspheute.com/artikel/20020618.htm
Die String Klasse in C#
http:/www.aspheute.com/artikel/20000803.htm
Einführung in ASP.NET Web Forms
http:/www.aspheute.com/artikel/20000808.htm
Einträge numerieren im DataGrid
http:/www.aspheute.com/artikel/20040317.htm
Eventbehandlung bei ASP.NET WebForms
http:/www.aspheute.com/artikel/20000922.htm
Fernöstliche Formulare / Unicode die Zweite
http:/www.aspheute.com/artikel/20001010.htm
Pager- und Footerzeilen des DataGrid erweitern
http:/www.aspheute.com/artikel/20040318.htm
Unicode und ASP (Einführung)
http:/www.aspheute.com/artikel/20000831.htm
Wochenberechnung mit .NET
http:/www.aspheute.com/artikel/20020905.htm
©2000-2006 AspHeute.com
Alle Rechte vorbehalten. Der Inhalt dieser Seiten ist urheberrechtlich geschützt.
Eine Übernahme von Texten (auch nur auszugsweise) oder Graphiken bedarf unserer schriftlichen Zustimmung.