Как установить и изменить язык и региональные параметры в WPF


у меня есть приложение .NET 4.0 WPF, где пользователь может изменить язык (язык и региональные параметры) Я просто позволяю пользователю выбрать язык, создать соответствующий CultureInfo и установить:

Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;

в коде C# это работает нормально. Однако в элементах управления WPF культура по-прежнему en-US. Это означает, например, что даты будут показаны в формате США вместо того, что правильно для текущей культуры.

по-видимому, это не ошибка. Согласно MSDN и нескольким сообщения в блогах и статьи на StackOverflow язык WPF не соответствует автоматически текущему языку и региональным параметрам. Это en-US, пока вы не сделаете это:

FrameworkElement.LanguageProperty.OverrideMetadata(
    typeof(FrameworkElement),
    new FrameworkPropertyMetadata(
        XmlLanguage.GetLanguage(CultureInfo.CurrentUICulture.IetfLanguageTag)));

см., например, проблемы локализации StringFormat в wpf.

Я не совсем понимаю, что здесь происходит. Похоже, что свойство Language для всех frameworkelements имеет значение текущего языка и региональных параметров. Во всяком случае, это работает. Я делаю это, когда приложение запускается, и теперь все элементы управления работают так, как ожидалось, и, например, даты форматируется в соответствии с текущей культурой.

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

7 51

7 ответов:

я собираюсь перезвонить здесь.

я успешно сделал это с помощью OverrideMetadata() метод, который ОП упомянул:

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 собственность от .

и поэтому очевидным решением было переопределить метаданные на FrameworkContentElement точно так же. Увы, doing do бросил исключение (PropertyMetadata уже зарегистрирован для системы типов.Окна.FrameworkContentElement), и поэтому я должен был сделать это на следующем потомке предка Run вместо System.Windows.Documents.TextElement:

FrameworkContentElement.LanguageProperty.OverrideMetadata(
  typeof(System.Windows.Documents.TextElement), 
  new FrameworkPropertyMetadata(lang)
);

и это разобрало все мои проблемы.

есть еще несколько подклассов FrameworkContentElement (перечислены здесь), которые для полноты должны иметь свои метаданные переопределены, а также.

Я не уверен, как обойти исключение "не могу вызвать OverrideMetadata несколько раз".

в качестве обходного пути, когда пользователь изменяет язык и региональные параметры пользовательского интерфейса в вашем приложении, вы можете перезапустить приложение с этим языком и региональными параметрами, передав новый язык и региональные параметры в качестве аргумента командной строки. Если ваши пользователи не будут часто менять культуру, это звучит как разумное решение.

Я никогда не нашел способ сделать именно то, что я просил в вопросе. В моем случае я решил его, имея все мои usercontrols наследуют от суперкласса, который содержал это:

/// <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's constructor is executed before the type's 
        // own constructor (which call InitializeComponent()) this is as it 
        // should be for classes extending this
        this.Language = XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag);
    }
}

когда пользователь изменяет язык, я затем создаю новые экземпляры любых usercontrols, которые в настоящее время работают.

это решило мою проблему. Тем не менее, я все равно хотел бы способ сделать это "автоматически" (т. е. без необходимости отслеживать какие-либо экземпляры объекты.)

только мои два цента: после почти сошел с ума при попытке реализовать ComponentOne WPF элементы управления (DataGrid и C1DatePicker) с моим немецким языком сборки я наткнулся на эту страницу.

это, кажется, направление в правильном направлении: я только что ввел приведенный выше код в мое приложение.код 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-работает! Это решило все проблемы, которые у меня были с международными настройками даты / времени в моих приложениях. Отлично!

У меня в значительной степени была та же проблема.

Я нашел это: http://www.codeproject.com/Articles/35159/WPF-Localization-Using-RESX-Files (не может быть первоисточником).

в нем рассматривается расширение разметки с именем "UICultureExtension", которое присоединяется к свойству языка всех элементов фреймворка, нуждающихся в локализации (в XAML).

Если вы создадите событие изменения языка пользовательского интерфейса, статические менеджеры расширений в фоновом режиме будут обновляться все зарегистрированные элементы фреймворка.

Adaptive OverrideMetadata

некоторая форма перезагрузки неизбежна, потому что изменение элемента управления 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 вашей windows это будет принудительно, а затем каждый новый элемент управления увидит, что он наследует значение от своего родителя и принудит его. В результате новые элементы управления, добавленные в это окно, будут использовать текущий язык.

PS Как и многие вещи в WPF, было бы немного проще, если бы они не были так заинтересованы, чтобы держать вещи internal. DefaultValueFactory было бы гораздо больше элегантный способ сделать это.

перезагрузка

самый экстремальный, но поэтому надежный способ перезагрузки - это просто создать новое главное окно и отбросить старое.

почти так же экстремально, но не совсем, чтобы изменить языковую настройку только в очень простой панели главного окна с очень малой загрузкой, и это очень мало, чтобы быть полностью привязанным к viewmodel, который поддерживает принудительное уведомление об изменении свойства для всё.

существующие ответы на этот вопрос и другие предложения.

Это не совсем ответ, но я использовал это, чтобы перезагрузить ресурсы. Но вам все равно нужно перезагрузить окна...

 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);
        }