Подтвердить что ты не робот

Как установить и изменить культуру в 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 и, похоже, у него там что-то работает. Однако я не могу представить, как это сделать в моем приложении. Кажется, мне нужно будет обновить язык во всех открытых окнах и элементах управления и обновить все существующие привязки и т.д.)

4b9b3361

Ответ 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

Адаптивное OverrideMetadata​​h2 >

Некоторая форма перезагрузки неизбежна, поскольку изменение элемента управления 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);
        }