Minimalne wymagania:
- Klasy: CultureInfo, RegionInfo, DateTimeFormatInfo, NumberFormatInfo, CompareInfo, CultureAndRegionInfoBuilder,
- Powiązane wyliczenia: CultureTypes, NumberStyles, CompareOptions, CultureAndRegionModifier.
Globalizacja - proces tworzenia aplikacji, która obsługuje zlokalizowane interfejsy użytkownika i dane regionalne dla użytkowników w wielu kulturach. Lokalizacja - proces tłumaczenia zasobów aplikacji na lokalizowane wersje dla każdej kultury, które aplikacja będzie obsługiwać. Egzamin skupia się na formatowaniu i porównywaniu danych w różnych kulturach.
Co to jest kultura? Definiuje ona kalendarze, konwencje formatowania danych, czasu, liczb, walut a także zasady sortowania. Administratorzy ustawiają bieżącą kulturę, jednak można ją unieważnić w obrębie bieżącej aplikacji - 2 własności klasy Thread (pobierz przez Thread.CurrentThread):
- public CultureInfo CurrentCulture { get; set; },
- public CultureInfo CurrentUICulture { get; set; }.
Odnośnie ostatniej własności należy zaznaczyć, że obiekt CultureInfo przez nią zwracany może być neutralną kulturą. Neutralne kultury nie powinny być używane z metodami formatującymi takimi jak String.Format(IFormatProvider, String, Object[]), DateTime.ToString(String, IFormatProvider) i Convert.ToString(Char, IFormatProvider). Przykład:
public static void Main()
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("pl-PL"); // kultura dla bieżącego wątku
Thread.CurrentThread.CurrentUICulture = new CultureInfo("es-ES"); // kultura używana przez Resource Managera do określania zasobów dla określonej kultury podczas runtime'u
double d = 1234567.89;
MessageBox.Show(d.ToString("C")); // w zł, a nie w euro
MessageBox.Show(d.ToString("C", new CultureInfo("en-GB"))); // string format, IFormatProvider provider; w funtach, inne separatory (dziesiętny i zmiennoprzecinkowy)
wypiszKultury(CultureTypes.AllCultures); // wszystkie: neutralne, specyficzne, zainstalowane w Windows i utworzone przez użytkownika
wypiszKultury(CultureTypes.NeutralCultures); // neutralne - powiązane tylko z językiem, a nie z określonym krajem/regionem (tylko język)
wypiszKultury(CultureTypes.SpecificCultures); // specyficzne/konkretne - dla kraju/regionu (język + regionalne definicje formatowania)
long acctNumber = 104254567890;
Console.WriteLine(String.Format(new AcctNumberFormat(), "{0:H}", acctNumber)); // IFormatProvider provider, string format, Object[] args
Console.WriteLine(String.Format(new AcctNumberFormat(), "{0}", acctNumber));
acctNumber = 14567890;
Console.WriteLine(String.Format(new AcctNumberFormat(), "{0:H}", acctNumber));
Console.WriteLine(String.Format(new AcctNumberFormat(), "{0}", acctNumber));
acctNumber = 18779887654111;
Console.WriteLine(String.Format(new AcctNumberFormat(), "{0:H}", acctNumber));
Console.WriteLine(String.Format(new AcctNumberFormat(), "{0}", acctNumber));
}
static void wypiszKultury(CultureTypes t)
{
CultureInfo[] cis = CultureInfo.GetCultures(t);
Console.WriteLine("---" + t.ToString() + "---");
for (int i = 0; i < cis.Length; i++)
{
if (i == 25)
break;
else
Console.WriteLine(cis[i].DisplayName + " [" + cis[i].EnglishName + "]");
}
}
public class AcctNumberFormat : IFormatProvider, ICustomFormatter
{
private const int ACCT_LENGTH = 12;
public object GetFormat(Type formatType) // IFormatProvider
{
if (formatType == typeof(ICustomFormatter))
return this;
else
return null;
}
public string Format(string fmt, object arg, IFormatProvider formatProvider) // ICustomFormatter
{
string result = arg.ToString();
if (result.Length < ACCT_LENGTH)
result = result.PadLeft(ACCT_LENGTH, '0'); // wypełnij zerami
if (result.Length > ACCT_LENGTH)
result = result.Substring(0, ACCT_LENGTH); // obetnij
if (!String.IsNullOrEmpty(fmt) && fmt.ToUpper() == "H")
return result.Substring(0, 5) + "-" + result.Substring(5, 3) + "-" + result.Substring(8);
else
return result;
}
}
Interfejs IFormatProvider dostarcza mechanizmu do pobierania obiektu do kontroli formatowania. Implementowany m.in. przez NumberFormatInfo, DateTimeFormatInfo i CultureInfo.
Klasa CultureInfo ma m.in. następujące własności:
- public virtual CompareInfo CompareInfo { get; } - definiuje jak porównywać stringi dla kultury (tylko get!),
- public virtual DateTimeFormatInfo DateTimeFormat { get; set; } - odpowiedni format dla wyświetlania dat i czasów,
- public static CultureInfo InvariantCulture { get; } - powiązana z językiem angielskim, ale nie powiązana z żadnym krajem/regionem,
- public virtual NumberFormatInfo NumberFormat { get; set; } - odpowiedni format dla wyświetlania liczb, waluty i procentów,
- public virtual TextInfo TextInfo { get; } - definiuje system pisania (writing system) powiązany z kulturą,
- public bool UseUserOverride { get; } - czy bieżąca CultureInfo używa ustawień kultury wybranej przez użytkownika (użytkownik może unieważnić niektóre wartości powiązane z bieżącą kulturą Windows w Panelu Sterowania).
DateTimeFormatInfo i NumberFormatInfo może być utworzona tylko dla niezmiennej (invariant) lub dla specyficznej kultury (culture) /nie dla neutralnych/.
Klasy NumberFormatInfo i DateTimeFormatInfo definiują jak odpowiednio wartości liczbowe i wartości DateTime są formatowane i wyświetlane w zależności od kultury.
public static void Main()
{
StringBuilder sb = new StringBuilder();
foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.SpecificCultures))
{
if (ci.TwoLetterISOLanguageName != "en")
continue;
NumberFormatInfo nfi = ci.NumberFormat;
sb.AppendFormat("The currency symbol for '{0}' is '{1}'", ci.DisplayName, nfi.CurrencySymbol);
sb.AppendLine();
}
MessageBox.Show(sb.ToString());
double d = 3.145678;
CultureInfo ci_pl_PL = new CultureInfo("pl-PL");
ci_pl_PL.NumberFormat.CurrencySymbol = "$";
Thread.CurrentThread.CurrentCulture = ci_pl_PL; // musisz stworzyć nową i przypisać
Console.WriteLine("C", d); // niepoprawne - wypisze znak 'C'
Console.WriteLine("{0:C}", d); // 3,15 $
Console.WriteLine(d.ToString("C")); // 3,15 $
ci_pl_PL.NumberFormat.CurrencyDecimalDigits = 3;
Console.WriteLine(d.ToString("C")); // 3,146 $
ci_pl_PL.NumberFormat.PercentPositivePattern = 0;
Console.WriteLine(d.ToString("p4")); // bez klamer; 314,5678 %
ci_pl_PL.NumberFormat.PercentPositivePattern = 1; // zwróć uwagę na brak spacji pomiędzy liczbą a znakiem %
Console.WriteLine(d.ToString("p2")); // 314,57%
NumberFormatInfo nfi_en_US = new CultureInfo("en-US", false).NumberFormat; // bool useUserOverride
Int64 myInt64 = 123456789012345;
Console.WriteLine(myInt64.ToString("C", nfi_en_US)); // $123,456,789,012,345.00
int[] mySizes1 = { 2, 3, 4 };
nfi_en_US.CurrencyGroupSizes = mySizes1;
Console.WriteLine(myInt64.ToString("C", nfi_en_US)); // $12,3456,7890,123,45.00
DateTimeFormatInfo dtfi = new CultureInfo("en-US", false).DateTimeFormat; // false - używa domyślnych ustawień kultury
Console.WriteLine(dtfi.GetAbbreviatedDayName(DayOfWeek.Monday)); // Mon
// poniższe to gettery/settery
Console.WriteLine(dtfi.AMDesignator); // AM
Console.WriteLine(dtfi.LongDatePattern); // D -> dddd, MMMM dd, yyyy
Console.WriteLine(dtfi.LongTimePattern); // T -> h:mm:ss tt
Console.WriteLine(dtfi.MonthDayPattern); // m/M -> MMMM dd
Console.WriteLine(dtfi.ShortDatePattern); // d -> M/d/yyyy
Console.WriteLine(dtfi.ShortTimePattern); // t -> h:mm tt
Console.WriteLine(dtfi.SortableDateTimePattern); // s -> yyyy'-'MM'-'dd'T'HH':'mm':'ss
Console.WriteLine(dtfi.UniversalSortableDateTimePattern); // u -> yyyy'-'MM'-'dd HH':'mm':'ss'Z'
Console.WriteLine(dtfi.YearMonthPattern); // y/Y -> MMMM, yyyy
double value = 12345.6789;
// C lub c = Currency
Console.WriteLine(value.ToString("C3", new CultureInfo("en-US"))); // $12,345.679
Console.WriteLine(value.ToString("C3", CultureInfo.CreateSpecificCulture("en-US"))); // $12,345.679
// D lub d = Decimal
try
{
Console.WriteLine(value.ToString("D"));
}
catch (FormatException)
{
Console.WriteLine("FormatException");
}
int value_ing = 12345;
Console.WriteLine(value_ing.ToString("D")); // tylko całkowite; 12345
Console.WriteLine(value_ing.ToString("D8")); // 00012345
// F lub f = Fixed-point
double integerNumber = -29541;
Console.WriteLine(integerNumber.ToString("F3", CultureInfo.InvariantCulture)); // -29541.000
// placeholdery:
Double myDouble = 1234567890;
Console.WriteLine(myDouble.ToString("(###) ### - ####")); // (123) 456 - 7890
value = .086;
Console.WriteLine(value.ToString("#0.##%", CultureInfo.InvariantCulture)); // 8.6%
value = 1.2;
Console.WriteLine(value.ToString("0.00", CultureInfo.InvariantCulture)); // 1.20
Console.WriteLine(value.ToString("00.00", CultureInfo.InvariantCulture)); // 01.20
Console.WriteLine(value.ToString("00.00", CultureInfo.CreateSpecificCulture("da-DK"))); // 01,20
}
Klasa CompareInfo implementuje zbiór metod dla porównywania ciągów z zachowaniem informacji o kulturze. Są 2 możliwości pobrania obiektu CompareInfo: użycie własności CultureInfo.CompareInfo lub metody CompareInfo.GetCompareInfo. Operacje porównywania (m.in. te wykonywane przez IndexOf lub LastIndexOf) mogą dać nieoczekiwane wyniki, jeśli szukana wartość jest ignorowana (nawiasem mówiąc jeśli szukasz pustego ciągu w IndexOf, to zwrócona wartość jest równa 0). Powinieneś/aś używać InvariantCulture do zapewnienia spójnego zachowania niezależnie od ustawień kultury OSa - 2 sposoby:
- Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture,
- s.IndexOf("AE", StringComparison.InvariantCulture));
Różne kultury wykonują sortowanie w różny sposób np. dla znaku AE:
- szwedzki: po Z,
- niemiecki: po A,
- angielski (znak specjalny): przed A.
Co ciekawe niektóre kultury obsługują więcej niż jeden porządek sortowania np. zh-CN (chiński, Chiny). Przykład:
public static void Main()
{
string[] words = new string[] { "Apple", "Æble" };
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
SortWords(words);
Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
SortWords(words);
words = new string[] { "AEble", "Æble" };
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
FindAE(words);
Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
FindAE(words);
Thread.CurrentThread.CurrentCulture = new CultureInfo(0x00000407); // de-DE, Dictionary sort
Thread.CurrentThread.CurrentCulture = new CultureInfo(0x00010407); // de-DE, Phone Book Sort DIN sort
// dla zh-CN: Pronunciation: 0x00000804, Stroke Count: 0x00020804
String[] sign = new String[] { "<", "=", ">" };
String s1 = "Coté", s2 = "coté", s3 = "côte";
CompareInfo ci = new CultureInfo("fr-FR").CompareInfo;
Console.WriteLine("The LCID for {0} is {1}.", ci.Name, ci.LCID);
Console.WriteLine("fr-FR Compare: {0} {2} {1}", s1, s2, sign[ci.Compare(s1, s2, CompareOptions.IgnoreCase) + 1]);
Console.WriteLine("fr-FR Compare: {0} {2} {1}", s2, s3, sign[ci.Compare(s2, s3, CompareOptions.None) + 1]);
String myStr1 = "calle";
String myStr2 = "llegar";
String myXfix = "lle";
CompareInfo myComp = CultureInfo.InvariantCulture.CompareInfo;
Console.WriteLine("IsPrefix( {0}, {1} ) : {2}", myStr1, myXfix, myComp.IsPrefix(myStr1, myXfix));
Console.WriteLine("IsPrefix( {0}, {1} ) : {2}", myStr2, myXfix, myComp.IsPrefix(myStr2, myXfix));
Console.WriteLine("IsSuffix( {0}, {1} ) : {2}", myStr1, myXfix, myComp.IsSuffix(myStr1, myXfix));
Console.WriteLine("IsSuffix( {0}, {1} ) : {2}", myStr2, myXfix, myComp.IsSuffix(myStr2, myXfix));
Console.WriteLine(String.Compare("Apple", "Æble", false, CultureInfo.InvariantCulture));
Console.WriteLine(String.Compare("Apple", "Æble", false, CultureInfo.GetCultureInfo("da-DK")));
string str = "co", str2 = "co";
Console.WriteLine(str.CompareTo(str2));
String num = "A";
int val = int.Parse(num, NumberStyles.HexNumber);
Console.WriteLine("{0} in hex = {1} in decimal.", num, val);
num = " -45 ";
val = int.Parse(num, NumberStyles.AllowLeadingSign | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite);
Console.WriteLine("'{0}' parsed to an int is '{1}'.", num, val);
}
private static void SortWords(string[] words)
{
Console.WriteLine("-> " + Thread.CurrentThread.CurrentCulture);
Array.Sort(words);
foreach (string s in words)
Console.WriteLine(s);
}
private static void FindAE(string[] words)
{
Console.WriteLine("-> " + Thread.CurrentThread.CurrentCulture);
Array.Sort(words);
foreach (string s in words)
{
Console.WriteLine(" AE in {0}: {1}", s, s.IndexOf("AE"));
Console.WriteLine(" Æ in {0}: {1}", s, s.IndexOf("Æ"));
}
}
Wynik działania:
-> en-US
Able
Apple
-> da-DK
Apple
Able
-> en-US
AE in Able: 0
A in Able: 0
AE in AEble: 0
A in AEble: 0
-> da-DK
AE in AEble: 0
A in AEble: -1
AE in Able: -1
A in Able: 0
The LCID for fr-FR is 1036.
fr-FR Compare: Coté = coté
fr-FR Compare: coté > côte
IsPrefix( calle, lle ) : False
IsPrefix( llegar, lle ) : True
IsSuffix( calle, lle ) : True
IsSuffix( llegar, lle ) : False
1
-1
0
A in hex = 10 in decimal.
' -45 ' parsed to an int is '-45'.
Klasa RegionInfo zawiera informacje o kraju/regionie. W przeciwieństwie do CultureInfo nie reprezentuje preferencji użytkownika i nie polega na języku lub kulturze użytkownika. Podczas tworzenia obiektu RegionInfo powinieneś używać “pełnej” nazwy np. en-US zamiast samego kodu kraju np. US.
RegionInfo myRI1 = new RegionInfo("US"); // lub en-US
Console.WriteLine("Name: {0}", myRI1.Name);
Console.WriteLine("DisplayName: {0}", myRI1.DisplayName);
Console.WriteLine("EnglishName: {0}", myRI1.EnglishName);
Console.WriteLine("IsMetric: {0}", myRI1.IsMetric);
Console.WriteLine("ThreeLetterISORegionName: {0}", myRI1.ThreeLetterISORegionName);
Console.WriteLine("ThreeLetterWindowsRegionName: {0}", myRI1.ThreeLetterWindowsRegionName);
Console.WriteLine("TwoLetterISORegionName: {0}", myRI1.TwoLetterISORegionName);
Console.WriteLine("CurrencySymbol: {0}", myRI1.CurrencySymbol);
Console.WriteLine("ISOCurrencySymbol: {0}\n", myRI1.ISOCurrencySymbol);
RegionInfo myRI2 = new RegionInfo(new CultureInfo("en-US", false).LCID);
if (myRI1.Equals(myRI2))
Console.WriteLine("The two RegionInfo instances are equal."); // będą równe
else
Console.WriteLine("The two RegionInfo instances are NOT equal.");
Ostatnią klasą do omówienia jest CultureAndRegionInfoBuilder. Jak nazwa wskazuje umożliwia ona definiowanie własnych kultur bazujących na innych lub całkiem nowych kulturach i krajach/regionach. Później taka kultura może być zainstalowana (zarejestrowana - tylko z prawami admina) na komputerze i używana także przez inne aplikacje. Klasa znajduje się w sysglobl.dll /zakładka .NET/. Członki (wybrane):
- public CultureAndRegionInfoBuilder(string cultureName, CultureAndRegionModifiers flags); flagi CultureAndRegionModifiers:
- None - określona dodatkowa kultura,
- Neutral - neutralna,
- Replacement - zamienia istniejącą kulturę .NET Framework lub ustawienia lokalne (locale) Windows,
- public void LoadDataFromCultureInfo(CultureInfo culture),
- public void LoadDataFromRegionInfo(RegionInfo region),
- public void Register() - zapisuje bieżący obiekt i udostępnia kulturę aplikacjom (uprawnienia admina - więc najlepiej podczas instalacji),
- public static void Unregister(string cultureName) - usuwa powyższą (z %WINDIR%\Globalization),
- public void Save(string filename) - zapisuje reprezentację XML do pliku,
- public static CultureAndRegionInfoBuilder CreateFromLdml(string xmlFileName) - odczytuje powyższą,
- public CompareInfo CompareInfo { get; set; },
- public CultureInfo ConsoleFallbackUICulture { get; set; } - alternatywna kultura UI odpowiednia dla aplikacji konsolowych,
- public CultureTypes CultureTypes { get; },
- public string CurrencyNativeName { get; set; },
- public bool IsMetric { get; set; },
- public NumberFormatInfo NumberFormat { get; set; },
- public int LCID { get; },
- public string RegionEnglishName { get; set; },
- public TextInfo TextInfo { get; set; },
- public string TwoLetterISOLanguageName { get; set; }.
Przykład:
CultureAndRegionInfoBuilder cib = new CultureAndRegionInfoBuilder("pl-US", CultureAndRegionModifiers.None);
cib.LoadDataFromCultureInfo(new CultureInfo("pl-PL"));
cib.LoadDataFromRegionInfo(new RegionInfo("US"));
cib.CultureEnglishName = "Polish Greenpoint";
cib.CultureNativeName = "Polski Greenpoint";
cib.IsMetric = true;
cib.ISOCurrencySymbol = "PLN";
cib.RegionEnglishName = "Polish Greenpoint Region";
cib.RegionNativeName = "Region polski Greenpoint";
cib.Register();
CultureInfo ci = new CultureInfo("pl-US");
Console.WriteLine("Name: . . . . . . . . . . . . . {0}", ci.Name);
Console.WriteLine("EnglishName:. . . . . . . . . . {0}", ci.EnglishName);
Console.WriteLine("NativeName: . . . . . . . . . . {0}", ci.NativeName);
Console.WriteLine("TwoLetterISOLanguageName: . . . {0}", ci.TwoLetterISOLanguageName);
Console.WriteLine("ThreeLetterISOLanguageName: . . {0}", ci.ThreeLetterISOLanguageName);
Console.WriteLine("ThreeLetterWindowsLanguageName: {0}", ci.ThreeLetterWindowsLanguageName);
CultureAndRegionInfoBuilder.Unregister("pl-US");
Do końca zostały jeszcze 2 przyjemne tematy: rysowanie i obsługa tekstów.