代码之家  ›  专栏  ›  技术社区  ›  Torben Junker Kjær

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

  •  52
  • Torben Junker Kjær  · 技术社区  · 14 年前

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

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

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

    显然,这不是一个bug。根据msdn和stackoverflow上的一些博客文章和文章,WPF语言不会自动遵循当前的区域性。在你这样做之前,我们一直在努力:

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

    参见例如 StringFormat Localization issues in wpf .

    我不完全理解这里发生了什么。似乎所有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 回复  |  直到 9 年前
        1
  •  8
  •   Ross    11 年前

    我要在这里插话。

    我用 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 属性从 System.Windows.FrameworkContentElement 相反。

    所以显而易见的解决方案是重写 FrameworkContentElement 以同样的方式。唉,做了就有例外了( 已为System.Windows.FrameworkContentElement类型注册了PropertyMetadata 所以我必须在 Run 相反, System.Windows.Documents.TextElement :

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

    这解决了我所有的问题。

    还有几个子类 框架内容元素 (上市) here )为了完整性,也应该覆盖它们的元数据。

        2
  •  5
  •   Judah Gabriel Himango    14 年前

    我不知道如何绕过“不能多次调用overrideMetadata”异常。

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

        3
  •  5
  •   MoonKnight    12 年前

    我从来没有找到一种方法去做我在这个问题上要求的事情。 在我的例子中,我通过让我的所有用户控件继承一个包含以下内容的超类来解决它:

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

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

    这解决了我的问题。但是,我仍然希望有一种“自动”完成这项工作的方法(即不必跟踪任何实例化的对象)。

        4
  •  4
  •   RJ Moeller    12 年前

    就我的两分钱:在尝试用德语程序集实现componentone wpf控件(datagrid和c1datepicker)时,我几乎疯了,但我偶然发现了这个页面。

    这似乎是正确的方向:我刚刚将上述代码输入到app.xaml.cs/application的启动例程中,现在c1datepicker的德语日期/时间格式终于工作了。

    在那之后测试数据报。

        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-有效!这解决了我在应用程序中使用国际日期/时间设置时遇到的所有问题。伟大的!

        5
  •  3
  •   rbee    11 年前

    我也有同样的问题。

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

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

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

        6
  •  3
  •   Peter Taylor    9 年前

    自适应覆盖元数据

    某些形式的重新加载是不可避免的,因为更改控件的 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="" 在Windows的XAML中,这将被强制执行,然后每个新控件都将看到它从其父控件继承了一个值,并将强制执行该值。结果是,添加到该窗口的新控件将使用当前语言。

    PS和WPF中的很多东西一样,如果他们不那么热衷于保存东西的话,那就简单多了。 internal . DefaultValueFactory 这将是一种更优雅的方式。

    重装

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

    几乎同样极端,但并非完全是安排语言设置只在主窗口的一个非常简单的窗格中进行更改,加载量非常小,并且几乎不完全数据绑定到支持强制对所有内容进行属性更改通知的ViewModel。

    对这个问题的现有答案还有其他的建议。

        7
  •  1
  •   Emmanuel    14 年前

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

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