Как установить и изменить культуру в WPF
У меня есть приложение .NET 4.0 WPF, где пользователь может изменить язык (культуру)
Я просто позволю пользователю выбрать язык, создать соответствующий CultureInfo и установить:
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;
В коде С# это отлично работает. Однако в WPF контролирует культуру по-прежнему в США. Это означает, например, что даты будут отображаться в формате США вместо того, что будет правильным для текущей культуры.
По-видимому, это не ошибка. Согласно MSDN и нескольким сообщениям в блоге и статьям о StackOverflow, язык WPF автоматически не отслеживает текущую культуру. Это en-US, пока вы этого не сделаете:
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(
XmlLanguage.GetLanguage(CultureInfo.CurrentUICulture.IetfLanguageTag)));
См. например Проблемы с локализацией строки StringFormat в wpf.
Я не совсем понимаю, что здесь происходит. Кажется, что свойство Language на всех элементах структуры задано текущей культурой. Во всяком случае, он работает. Я делаю это, когда приложение запускается, и теперь все элементы управления работают так, как ожидалось, и, например, даты отформатированы в соответствии с текущей культурой.
Но теперь проблема: по MSDN FrameworkElement.LanguageProperty.OverrideMetadata
можно вызывать только один раз. И действительно, если я снова его назову (когда пользователь меняет язык), это вызовет исключение. Поэтому я не решил свою проблему.
Вопрос: как я могу достоверно обновлять культуру в WPF более одного раза и в любое время в жизненном цикле приложений?
(Я нашел это при исследовании: http://www.nbdtech.com/Blog/archive/2009/03/18/getting-a-wpf-application-to-pick-up-the-correct-regional.aspx
и, похоже, у него там что-то работает. Однако я не могу представить, как это сделать в моем приложении. Кажется, мне нужно будет обновить язык во всех открытых окнах и элементах управления и обновить все существующие привязки и т.д.)
Ответы
Ответ 1
Я никогда не нашел способ сделать именно то, что я просил в вопросе.
В моем случае я решил это решить, получив все мои пользовательские элементы, наследуемые от суперкласса, который содержал это:
/// <summary>
/// Contains shared logic for all XAML-based Views in the application.
/// Views that extend this type will have localization built-in.
/// </summary>
public abstract class ViewUserControl : UserControl
{
/// <summary>
/// Initializes a new instance of the ViewUserControl class.
/// </summary>
protected ViewUserControl()
{
// This is very important! We make sure that all views that inherit
// from this type will have localization built-in.
// Notice that the following line must run before InitializeComponent() on
// the view. Since the supertype constructor is executed before the type
// own constructor (which call InitializeComponent()) this is as it
// should be for classes extending this
this.Language = XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag);
}
}
Когда пользователь меняет язык, я создаю новые экземпляры любых пользовательских контроллеров, которые в настоящее время запущены.
Это решило мою проблему. Тем не менее, я все равно хотел бы сделать это "автоматически" (то есть без необходимости отслеживать какие-либо объекты-объекты).
Ответ 2
Я собираюсь перезвонить здесь.
Я успешно сделал это, используя метод OverrideMetadata()
, о котором упомянул OP:
var lang = System.Windows.Markup.XmlLanguage.GetLanguage(MyCultureInfo.IetfLanguageTag);
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(lang)
);
Но я все еще нашел экземпляры в своем WPF, в котором система была применена для дат и значений чисел. Оказалось, что это значения в элементах <Run>
. Это происходило, потому что класс System.Windows.Documents.Run
не наследует от System.Windows.FrameworkElement
, поэтому переопределение метаданных на FrameworkElement
явно не имеет эффекта.
System.Windows.Documents.Run
вместо этого наследует его свойство Language
от System.Windows.FrameworkContentElement
.
Итак, очевидным решением было переопределить метаданные на FrameworkContentElement
таким же образом. Увы, сделайте исключение (PropertyMetadata уже зарегистрирован для типа System.Windows.FrameworkContentElement), и поэтому мне пришлось сделать это на следующем предка-потомке Run
вместо System.Windows.Documents.TextElement
:
FrameworkContentElement.LanguageProperty.OverrideMetadata(
typeof(System.Windows.Documents.TextElement),
new FrameworkPropertyMetadata(lang)
);
И это разобрало все мои проблемы.
Есть еще несколько подклассов FrameworkContentElement
(перечисленные здесь), которые для полноты также должны иметь переопределенные метаданные.
Ответ 3
Я не уверен, как обойти исключение "не могу вызвать OverrideMetadata несколько раз".
В качестве обходного пути, когда пользователь изменяет культуры пользовательского интерфейса в вашем приложении, вы можете перезапустить приложение с этой культурой, передав новую культуру в качестве аргумента командной строки. Если ваши пользователи часто меняют культуры, это звучит как разумное решение.
Ответ 4
Только мои два цента: после сумасшествия, когда пытались внедрить элементы управления WPF ComponentOne (DataGrid и C1DatePicker) с моей сборкой на немецком языке, я наткнулся на эту страницу.
Кажется, это правильное направление: я просто ввел вышеуказанный код в мою процедуру App.xaml.cs/Application_startup, и теперь немецкое форматирование даты и времени для C1DatePicker, наконец, работает.
Теперь нужно проверить DataGrid.
private void Application_Startup(object sender, StartupEventArgs e)
{
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(
System.Windows.Markup.XmlLanguage.GetLanguage(CultureInfo.CurrentUICulture.IetfLanguageTag)));
}
Спасибо!
Обновление: Протестировано C1DataGrid для WPF - работает! Это решило все проблемы, которые у меня были с международными настройками Дата/Время в моих Приложениях. Отлично!
Ответ 5
У меня в значительной степени была такая же проблема.
Я нашел это:
http://www.codeproject.com/Articles/35159/WPF-Localization-Using-RESX-Files
(возможно, не оригинальный источник).
В нем обсуждается расширение разметки с именем "UICultureExtension", которое привязано к свойству "Язык" всех элементов структуры, которым требуется локализация (в XAML).
Если вы измените событие с измененным интерфейсом пользовательского интерфейса, статические менеджеры по расширению в фоновом режиме обновят все зарегистрированные элементы фреймворка.
Ответ 6
Адаптивное OverrideMetadatah2 >
Некоторая форма перезагрузки неизбежна, поскольку изменение элемента управления Language
не делает его обновлением его текста.
Однако есть способ переопределить метаданные, которые позволяют вам установить один раз и новые элементы управления автоматически используют текущую культуру:
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(
System.Windows.Markup.XmlLanguage.Empty,
default(PropertyChangedCallback),
_CoerceCurrentXmlLang));
где CoerceValueCallback
-
private static object _CoerceCurrentXmlLang(DependencyObject d, object baseValue)
{
var lang = baseValue as System.Windows.Markup.XmlLanguage;
var culture = System.Globalization.CultureInfo.CurrentUICulture;
return lang != null && lang.IetfLanguageTag.Equals(culture.Name, StringComparison.InvariantCultureIgnoreCase)
? lang
: System.Windows.Markup.XmlLanguage.GetLanguage(culture.Name);
}
Само по себе этого недостаточно, потому что только что созданные элементы управления получат значение по умолчанию System.Windows.Markup.XmlLanguage.Empty
без принуждения. Однако, если вы затем установите xml:lang=""
в XAML окна, это будет принудительно, а затем каждый новый элемент управления увидит, что он наследует значение от своего родителя и будет принуждать его. В результате новые элементы управления, добавленные в это окно, будут использовать текущий язык.
PS Как и во многих вещах в WPF, было бы намного проще, если бы они не стремились хранить вещи internal
. DefaultValueFactory
был бы гораздо более элегантным способом сделать это.
перегрузочный
Самый экстремальный, но, следовательно, надежный способ перезагрузки - просто создать новое главное окно и отказаться от старого.
Практически столь же экстремально, но не совсем так, чтобы настройка языка была изменена только в очень простой панели основного окна с очень маленькой загрузкой и что совсем немного было полностью привязано к модели представления, которая поддерживает форсирование свойства измененное уведомление для всего.
Существующие ответы на этот вопрос имеют другие предложения.
Ответ 7
Это не полностью ваш ответ, но я использовал это для перезагрузки ресурсов. Но вам все равно нужно перезагрузить окна...
List<Uri> dictionaryList = new List<Uri>();
foreach (ResourceDictionary dictionary in Application.Current.Resources.MergedDictionaries)
{
dictionaryList.Add(dictionary.Source);
}
Application.Current.Resources.MergedDictionaries.Clear();
foreach (Uri uri in dictionaryList)
{
ResourceDictionary resourceDictionary1 = new ResourceDictionary();
resourceDictionary1.Source = uri;
Application.Current.Resources.MergedDictionaries.Add(resourceDictionary1);
}