Культура приложения
39C# и .NET --- Основы .NET --- Культура приложения
Чтобы посмотреть на все культуры в действии, создадим пример приложения Windows Presentation Foundation (WPF), которое умеет отображать все культуры и различные их свойства. На рисунке показано, как выглядит пользовательский интерфейс этого приложения в окне визуального конструктора приложений WPF в Visual Studio 2010:
Используйте следующую XAML-разметку:
<Window x:Class="CultureDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Culture Demo" Height="460" Width="700">
<Window.Resources>
<Style x:Key="baseControls" TargetType="{x:Type Control}">
<Setter Property='Margin' Value='5,5,5,5' />
</Style>
<Style TargetType="{x:Type Label}" BasedOn="{StaticResource baseControls}" />
<Style TargetType="{x:Type CheckBox}" BasedOn="{StaticResource baseControls}" />
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="5" />
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<GridSplitter HorizontalAlignment="Right" Name="gridSplitter1" Width="67" />
<TreeView SelectedItemChanged="treeCultures_SelectedItemChanged" Margin="5" Name="treeCultures" VerticalAlignment="Bottom" />
<Grid Grid.Column="1" Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="3*" />
<RowDefinition Height="3*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0">Culture Name:</Label>
<TextBlock Grid.Row="0" Grid.Column="1" x:Name="textCultureName" Width="100" />
<CheckBox Grid.Row="0" Grid.Column="2" x:Name="chkIsNeutral" HorizontalAlignment="Center" Content="Is Neutral" />
<Label Grid.Row="1" Grid.Column="0">English Name:</Label>
<TextBlock Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" x:Name="textEnglishName" />
<Label Grid.Row="2" Grid.Column="0">Native Name:</Label>
<TextBlock Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" x:Name="textNativeName" />
<Label Grid.Row="3" Grid.Column="0">Default Calendar:</Label>
<TextBlock Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" x:Name="textCalendar" />
<Label Grid.Row="4" Grid.Column="0">Optional Calendars:</Label>
<ListBox Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2" x:Name="listCalendars" />
<GroupBox x:Name="groupSamples" IsEnabled="False" Header="Samples" Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="3">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0">Number</Label>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="textNumber" />
<Label Grid.Row="1" Grid.Column="0">Full Date</Label>
<TextBlock Grid.Row="1" Grid.Column="1" x:Name="textDate" />
<Label Grid.Row="2" Grid.Column="0">Time</Label>
<TextBlock Grid.Row="2" Grid.Column="1" x:Name="textTime" />
</Grid>
</GroupBox>
<GroupBox x:Name="groupRegion" IsEnabled="False" Header="Region Information" Grid.Row="6" Grid.Column="0" Grid.ColumnSpan="3">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" >Region</Label>
<TextBlock Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" x:Name="textRegion" />
<Label Grid.Row="1" Grid.Column="0" >Currency</Label>
<TextBlock Grid.Row="1" Grid.Column="1" x:Name="textCurrency" />
<TextBlock Grid.Row="1" Grid.Column="2" x:Name="textCurrencyISO" />
<CheckBox Grid.Row="2" Grid.Column="1" x:Name="checkIsMetric">Is Metric</CheckBox>
</Grid>
</GroupBox>
</Grid>
</Grid>
</Window>
Во время инициализации этого приложения все доступные культуры добавляются в элемент управления типа древовидного представления (TreeView), размещенный в левой части окна приложения. Этот процесс инициализации происходит в методе AddCulturesToTree() , который вызывается в конструкторе класса окна CultureDemoWindow.
В методе AddCulturesToTree() сначала статическим методом CultureInfo.GetCultures() получается список всех культур. Передача этому методу аргумента CultureTypes.AllCultures приводит к возврату несортированного массива всех доступных культур. Далее этот массив сортируется с использованием лямбда-выражения, которое передается делегату сравнения во втором аргументе метода Array.Sort(). Затем в цикле foreach каждая из культур добавляется в древовидное представление. Для каждой из этих культур создается объект TreeViewItem, поскольку класс TreeView в WPF предусматривает использование именно этих объектов для отображения. Свойству Tag каждого из объектов TreeViewItem присваивается объект CultureInfo для обеспечения возможности получения к нему позже доступа из дерева.
Место внутри дерева, куда добавляется каждый объект TreeViewItem, зависит от типа представляемой им культуры. Если у этой культуры нет родительской культуры, производится добавление в корневой узел дерева. Для нахождения родительских культур все культуры запоминаются внутри словаря:
private void AddCulturesToTree()
{
var culturesByName = new Dictionary();
// Получаем все культуры
var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
Array.Sort(cultures, (c1, c2) => c1.Name.CompareTo(c2.Name));
var nodes = new TreeViewItem[cultures.Length];
int i = 0;
foreach (var ci in cultures)
{
nodes[i] = new TreeViewItem();
nodes[i].Header = ci.DisplayName;
nodes[i].Tag = ci;
culturesByName.Add(ci.Name, nodes[i]);
TreeViewItem parent;
if (!String.IsNullOrEmpty(ci.Parent.Name) &&
culturesByName.TryGetValue(ci.Parent.Name, out parent))
{
parent.Items.Add(nodes[i]);
}
else
{
treeCultures.Items.Add(nodes[i]);
}
i++;
}
}
При выборе пользователем узла внутри дерева вызывается обработчик события SelectedItemChanged элемента управления TreeView. Здесь этот обработчик реализован в методе TreeCultures_SelectedItemChanged(). Перед получением из дерева объекта CultureInfo через свойство Tag соответствующего объекта TreeViewItem производится очистка всех полей вызовом метода ClearTextFields(). Затем в некоторых из этих полей устанавливаются значения с использованием свойств Name, NativeName и EnglishName объекта CultureInfo. Если CultureInfo представляет нейтральную культуру, что может быть проверено с помощью свойства IsNeutralCulture, то устанавливается и соответствующий флажок:
private void treeCultures_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
ClearTextFields();
CultureInfo ci = (CultureInfo)((TreeViewItem)e.NewValue).Tag;
textCultureName.Text = ci.Name;
textNativeName.Text = ci.NativeName;
textEnglishName.Text = ci.EnglishName;
chkIsNeutral.IsChecked = ci.IsNeutralCulture;
...
Далее извлекается информация о принятом в данной культуре календаре. Свойство Calendar класса CultureInfo возвращает используемый по умолчанию объект Calendar для конкретной культуры. Поскольку у класса Calendar нет свойства, которое бы сообщало его имя, для получения имени этого класса используется метод ToString() базового класса. Из возвращаемой им строки удаляется наименование пространства имен, а результат отображается в текстовом поле textCalendar.
Поскольку в одной культуре может поддерживаться не один, а несколько календарей, с помощью свойства OptionalCalendars возвращается массив дополнительных поддерживаемых объектов Calendar. Эти дополнительные необязательные календари отображаются в окне списка listCalendars. Класс GregorianCalendar, унаследованный от базового класса Calendar, имеет дополнительное свойство CalendarType, которое предоставляет информацию о типе григорианского календаря. В зависимости от культуры в качестве типа может быть одно из значений перечисления GregorianCalendarTypes: Arabic, MiddleEastFrench, TransliteratedFrench, USEnglish или Localized. В случае григорианских календарей информация о типе тоже отображается в окне списка:
// календарь, используемый по умолчанию
textCalendar.Text = ci.Calendar.ToString().
Remove(0, 21).Replace("Calendar", "");
listCalendars.Items.Clear();
foreach (var optCal in ci.OptionalCalendars)
{
var calName = new StringBuilder(50);
calName.Append(optCal.ToString());
calName.Remove(0, 21);
calName.Replace("Calendar", "");
// в случае григорианского календаря добавление информации о его типе
GregorianCalendar gregCal = optCal as GregorianCalendar;
if (gregCal != null)
{
calName.AppendFormat(" {0}", gregCal.CalendarType.ToString());
}
listCalendars.Items.Add(calName.ToString());
}
Далее производится проверка на предмет того, является ли культура специфической (т.е. не нейтральной), за счет использования в операторе if условия !ci.IsNeutralCulture. Метод ShowSamples() отображает примеры форматирования чисел и дат. Этот метод реализуется в следующем разделе кода. Метод ShowRegionlnformation() применяется для отображения информации о регионе. В случае инвариантной культуры отображаться могут только примеры форматирования чисел и дат, но не информация о регионе. Инвариантная культура не имеет отношения ни к какому реальному языку, а потому не ассоциируется ни с каким регионом.
Ниже представлен весь код данной программы:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Windows;
using System.Windows.Controls;
namespace CultureDemo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
AddCulturesToTree();
}
private void AddCulturesToTree()
{
var culturesByName = new Dictionary<string, TreeViewItem>();
// get all cultures
var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
Array.Sort(cultures, (c1, c2) => c1.Name.CompareTo(c2.Name));
var nodes = new TreeViewItem[cultures.Length];
int i = 0;
foreach (var ci in cultures)
{
nodes[i] = new TreeViewItem();
nodes[i].Header = ci.DisplayName;
nodes[i].Tag = ci;
culturesByName.Add(ci.Name, nodes[i]);
TreeViewItem parent;
if (!String.IsNullOrEmpty(ci.Parent.Name) &&
culturesByName.TryGetValue(ci.Parent.Name, out parent))
{
parent.Items.Add(nodes[i]);
}
else
{
treeCultures.Items.Add(nodes[i]);
}
i++;
}
}
private void treeCultures_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
ClearTextFields();
// get CultureInfo object from tree
CultureInfo ci = (CultureInfo)((TreeViewItem)e.NewValue).Tag;
textCultureName.Text = ci.Name;
textNativeName.Text = ci.NativeName;
textEnglishName.Text = ci.EnglishName;
chkIsNeutral.IsChecked = ci.IsNeutralCulture;
// default calendar
textCalendar.Text = ci.Calendar.ToString().
Remove(0, 21).Replace("Calendar", "");
// fill optional calendars
listCalendars.Items.Clear();
foreach (var optCal in ci.OptionalCalendars)
{
var calName = new StringBuilder(50);
calName.Append(optCal.ToString());
calName.Remove(0, 21);
calName.Replace("Calendar", "");
// for GregorianCalendar add type information
GregorianCalendar gregCal = optCal as GregorianCalendar;
if (gregCal != null)
{
calName.AppendFormat(" {0}", gregCal.CalendarType.ToString());
}
listCalendars.Items.Add(calName.ToString());
}
// display number and date samples
if (!ci.IsNeutralCulture)
{
groupSamples.IsEnabled = true;
ShowSamples(ci);
// invariant culture doesn't have a region
if (String.Compare(ci.ThreeLetterISOLanguageName, "IVL", true) == 0)
{
groupRegion.IsEnabled = false;
}
else
{
groupRegion.IsEnabled = true;
ShowRegionInformation(ci.Name);
}
}
else // neutral culture: no region, no number/date formatting
{
groupSamples.IsEnabled = false;
groupRegion.IsEnabled = false;
}
}
private void ShowSamples(CultureInfo ci)
{
double number = 9876543.21;
textNumber.Text = number.ToString("N", ci);
DateTime today = DateTime.Today;
textDate.Text = today.ToString("D", ci);
DateTime now = DateTime.Now;
textTime.Text = now.ToString("T", ci);
}
private void ShowRegionInformation(string culture)
{
var ri = new RegionInfo(culture);
textRegion.Text = ri.DisplayName;
textCurrency.Text = ri.CurrencySymbol;
textCurrencyISO.Text = ri.ISOCurrencySymbol;
checkIsMetric.IsChecked = ri.IsMetric;
}
private void ClearTextFields()
{
textCultureName.Text = String.Empty;
textNativeName.Text = String.Empty;
textEnglishName.Text = String.Empty;
textCalendar.Text = string.Empty;
listCalendars.Items.Clear();
groupSamples.IsEnabled = false;
textNumber.Text = string.Empty;
textDate.Text = string.Empty;
textTime.Text = string.Empty;
groupRegion.IsEnabled = false;
textRegion.Text = string.Empty;
textCurrency.Text = string.Empty;
textCurrencyISO.Text = string.Empty;
}
}
}
Запустив это приложение, можно увидеть все доступные культуры в древовидном представлении в правой части окна. Выбор какой-нибудь культуры позволяет просматривать ее характеристики в левой части окна:
Сортировка
Порядок сортировки строк зависит от культуры. Некоторые культуры имеют разные порядки сортировки. Одним из примеров является финский язык, в котором V и W трактуются одинаково. В алгоритмах, сравнивающих строки для упорядочения, по умолчанию применяется сортировка, чувствительная к культуре.
Чтобы продемонстрировать подобное поведение на примере финского порядка сортировки, далее создается небольшое консольное приложение, в котором наименования некоторых штатов США сохраняются в массиве в несортированном виде. Планируется использовать классы из пространств имен System.Collections.Generic, System.Threading и System.Globalization, поэтому все эти пространства имен должны быть обязательно объявлены. Показанный ниже метод DisplayNames() применяется для отображения элементов массива или коллекции в окне консоли:
static void DisplayNames(string title, IEnumerable e)
{
Console.WriteLine(title);
foreach (string s in e)
Console.Write(s + "—");
Console.WriteLine();
Console.WriteLine();
}
В методе Main() после создания массива с названиями некоторых штатов США свойству потока CurrentCulture назначается культура Finnish, чтобы в следующем за ним вызове Array.Sort() использовался финский порядок сортировки строк. С помощью метода DisplayNames() наименования всех штатов отображаются в окне консоли:
static void Main()
{
string[] names = {"Alabama", "Texas", "Washington",
"Virginia", "Wisconsin", "Wyoming",
"Kentucky", "Missouri", "Utah", "Hawaii",
"Kansas", "Louisiana", "Alaska", "Arizona"};
Thread.CurrentThread.CurrentCulture = new CultureInfo("fi-FI");
Array.Sort(names);
DisplayNames("Sorted using the Finnish culture", names);
// Сортировка с помощью финской культуры
Array.Sort(names, System.Collections.Comparer.DefaultInvariant);
DisplayNames("Sorted using the invariant culture", names);
}
После первого отображения наименований некоторых штатов в порядке финской сортировки массив сортируется снова. При желании, чтобы сортировка выполнялась независимым от культуры пользователя образом, что может быть полезным при отправке массива на сервер или его сохранении в каком-то месте, можно использовать инвариантную культуру.