如何在WPF中设置和更改文化

时间:2010-10-28 08:48:24

标签: .net wpf localization

我有一个.NET 4.0 WPF应用程序,用户可以在其中更改语言(文化) 我只是让用户选择一种语言,创建一个相应的CultureInfo并设置:

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

在C#代码中,这很好用。然而,在WPF控制中,文化仍然是美国的。这意味着,例如,日期将以美国格式显示,而不是对当前文化正确的日期。

显然,这不是一个错误。根据MSDN和StackOverflow上的一些博客文章和文章,WPF语言不会自动遵循当前的文化。在你这样做之前它是en-US:

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

请参阅示例StringFormat Localization issues in wpf

我不完全明白这里发生了什么。似乎所有frameworkelements上的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 他似乎在那里工作。但是,我无法想象如何在我的应用程序中执行此操作。似乎我必须在所有打开的窗口和控件中更新语言并刷新所有现有的绑定等。)

8 个答案:

答案 0 :(得分:8)

我要来这里。

我使用OP提到的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.RunLanguage继承其System.Windows.FrameworkContentElement属性。

因此,显而易见的解决方案是以相同的方式覆盖FrameworkContentElement上的元数据。唉,这样做会引发异常( PropertyMetadata已经为类型System.Windows.FrameworkContentElement 注册了),所以我必须在Run的下一个后代祖先这样做,{{ 1}}:

System.Windows.Documents.TextElement

这解决了我的所有问题。

还有一些FrameworkContentElement.LanguageProperty.OverrideMetadata( typeof(System.Windows.Documents.TextElement), new FrameworkPropertyMetadata(lang) ); (列出here)的子类,为了完整性,它们的元数据也应该被覆盖。

答案 1 :(得分:5)

我不确定如何绕过“无法多次调用OverrideMetadata”异常。

作为一种解决方法,当用户更改应用中的UI文化时,您可以使用该文化重新启动应用,将新文化作为命令行参数传递。除非您的用户经常改变文化,否则这听起来像是一个合理的解决方案。

答案 2 :(得分:5)

我从来没有找到办法完全按照我在问题中提出的要求。 在我的情况下,我最终通过让我的所有usercontrol继承自包含它的超类来解决它:

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

当用户更改语言时,我会创建当前正在运行的任何用户控件的新实例。

这解决了我的问题。但是,我仍然想要一种“自动”的方法(即无需跟踪任何实例化的对象)。

答案 3 :(得分:4)

只是我的两分钱:在尝试使用我的德语语言程序集实现ComponentOne WPF控件(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)));
    }

谢谢!

更新:为WPF测试过C1DataGrid - 有效!这解决了我的应用程序中的国际日期/时间设置所遇到的所有问题。太好了!

答案 4 :(得分:3)

我几乎有同样的问题。

我发现了这个: http://www.codeproject.com/Articles/35159/WPF-Localization-Using-RESX-Files (可能不是原始来源)。

它讨论了一个名为“UICultureExtension”的标记扩展,它附加到需要本地化的所有框架元素的Language属性中(在XAML中)。

如果您提出了UI语言更改事件,后台的静态扩展管理器将更新所有已注册的框架元素。

答案 5 :(得分:3)

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而不会被强制。但是,如果您在Windows的XAML中设置了xml:lang="",那么它将被强制执行,然后每个新控件都会看到它从其父级继承一个值并强制它。结果是添加到该窗口的新控件将使用当前语言。

PS与WPF中的许多内容一样,如果他们不那么热衷于保留internal,那将会更加简单。 DefaultValueFactory这样做会更加优雅。

重新加载

最极端但又可靠的重新加载方式就是创建一个新的主窗口并丢弃旧的主窗口。

几乎同样极端但不完全是安排只在主窗口的一个非常简单的窗格中更改语言设置,加载很少,并且很少完全数据绑定到支持强制属性的视图模型改变了所有事情的通知。

此问题的现有答案还有其他建议。

答案 6 :(得分:1)

这不完全是你的答案,但我用它来重新加载资源。但是你仍然需要重装窗户......

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

答案 7 :(得分:0)

我使用合并的资源字典/动态资源方法来更改我的字符串。但是为了正确的自动日期/时间/其他区域格式 我在 Application.Current.MainWindow.Language 上设置语言,它被孩子们继承,并且 自动更新。至少在 Net 5.0 上...

var l = System.Globalization.CultureInfo.CreateSpecificCulture(localeName);
                
Thread.CurrentThread.CurrentUICulture = l;
Thread.CurrentThread.CurrentCulture = l;
System.Globalization.CultureInfo.DefaultThreadCurrentCulture = l;
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = l;
// Order is important!!! Set Threads before MainWindow.Language, 
// otherwise regional settings are inconsistent

Application.Current.MainWindow.Language = XmlLanguage.GetLanguage(l.Name);