如何设置和改变WPF中的文化

时间:2021-07-23 02:54:08

I have a .NET 4.0 WPF application where the user can change the language (culture) I simply let the user select a language, create a corresponding CultureInfo and set:

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

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

In the C# code this works fine. However in the WPF controls the culture is still en-US. This means for example that dates will be shown in the US format instead of whatever is correct for the current culture.

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

Apparently, this is not a bug. According to MSDN and several blog posts and articles on * the WPF language does not automatically follow the current culture. It is en-US until you do this:

显然,这不是一个bug。根据MSDN和一些关于*的博客文章和文章,WPF语言不会自动遵循当前的文化。在你这样做之前,它就是我们:

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

See for example StringFormat Localization issues in wpf.

参见wpf中的StringFormat本地化问题示例。

I do not completely understand what is going on here. It seems the Language property on all frameworkelements is set to the current culture. Anyway, it works. I do this when the application starts up and now all controls works as expected, and e.g. dates is formatted according to the current culture.

我不完全理解这里发生了什么。似乎所有frameworkelement上的语言属性都被设置为当前文化。无论如何,它的工作原理。当应用程序启动时,我这样做,现在所有的控件都按预期工作,例如date是根据当前的文化进行格式化的。

But now the problem: According to MSDN FrameworkElement.LanguageProperty.OverrideMetadata can only be called once. And indeed, if I call it again (when the user changes the language) it will throw an exception. So I haven't really solved my problem.

但是现在的问题是:根据MSDN FrameworkElement.LanguageProperty。OverrideMetadata只能调用一次。实际上,如果我再次调用它(当用户改变语言时),它将抛出一个异常。所以我没有真正解决我的问题。

The question: How can I reliably update the culture in WPF more than once and at any time in my applications life cycle?

问题是:在我的应用程序生命周期中,如何可靠地不止一次地更新WPF中的区域性?

(I found this when researching: http://www.nbdtech.com/Blog/archive/2009/03/18/getting-a-wpf-application-to-pick-up-the-correct-regional.aspx and it seems he has something working there. However, I can't imagine how to do this in my application. It seems I would have to update the language in all open windows and controls and refresh all existing bindings etc.)

(我在研究时发现了这一点:http://www.nbdtech.com/blog/archive/2009/03/18/getting-a -wpf- applicationto pick-up-the-correct-regional.aspx似乎他在那里工作。然而,我无法想象如何在我的应用程序中实现这一点。我似乎必须在所有打开的窗口和控件中更新语言,并刷新所有现有的绑定等等。

7 个解决方案

#1


8  

I'm going to chime in here.

我在这里插嘴。

I successfully did this using the OverrideMetadata() method that the OP mentioned:

我成功地使用了OP提到的OverrideMetadata()方法:

var lang = System.Windows.Markup.XmlLanguage.GetLanguage(MyCultureInfo.IetfLanguageTag);
FrameworkElement.LanguageProperty.OverrideMetadata(
  typeof(FrameworkElement), 
  new FrameworkPropertyMetadata(lang)
);

But, I still found instances in my WPF in which the system culture was being applied for dates and number values. It turned out these were values in <Run> elements. It was happening because the System.Windows.Documents.Run class does not inherit from System.Windows.FrameworkElement, and so the overriding of metadata on FrameworkElement obviously had no effect.

但是,我仍然在WPF中找到了应用系统文化的日期和数字值的实例。结果显示这些值都是 <运行> 元素。因为系统。windows。document。Run类不从System.Windows继承。FrameworkElement,因此,在FrameworkElement上重写元数据显然没有效果。

System.Windows.Documents.Run inherits its Language property from System.Windows.FrameworkContentElement instead.

System.Windows.Documents。Run从System.Windows继承它的语言属性。FrameworkContentElement代替。

And so the obvious solution was to override the metadata on FrameworkContentElement in the same way. Alas, doing do threw an exception (PropertyMetadata is already registered for type System.Windows.FrameworkContentElement), and so I had to do it on the next descendant ancestor of Run instead, System.Windows.Documents.TextElement:

显而易见的解决方案是用同样的方法覆盖FrameworkContentElement上的元数据。唉,做了就抛出了一个异常(PropertyMetadata已经注册为System.Windows.FrameworkContentElement类型),所以我必须在Run的下一个子祖先system . windows . document . textelement:

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

And that sorted out all my issues.

这就解决了我所有的问题。

There are a few more sub-classes of FrameworkContentElement (listed here) which for completeness should have their metadata overridden as well.

还有一些框架内容元素的子类(此处列出),为了完整性,它们的元数据也应该被重写。

#2


5  

I'm not sure how to get around the "can't call OverrideMetadata multiple times" exception.

我不知道如何避开“不能多次调用OverrideMetadata”异常。

As a workaround, when the user changes UI cultures in your app, you could restart your app with that culture, passing in the new culture as a command line argument. Unless your users will be changing cultures often, this sounds like a reasonable solution.

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

#3


5  

I never found a way to do exactly what I asked for in the question. In my case I ended up solving it by having all my usercontrols inherit from a superclass that contained this:

我从来没有找到一种方法去做我在问题中要求的事情。在我的例子中,我最终通过让我所有的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);
    }
}

When the user changes the language I then create new instances of any usercontrols that are currently running.

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

This solved my problem. However, I would still like a way to do this "automatically" (i.e. without having to keep track of any instantiated objects).

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

#4


4  

Just my two cents: After almost going crazy when trying to implement ComponentOne WPF controls (DataGrid and C1DatePicker) with my German language assembly I stumbled upon this page.

仅仅是我的两美分:当我试图在我的德语语言程序集上实现ComponentOne WPF控件(DataGrid和C1DatePicker)时,我几乎发疯了。

This seems to be directing in the right way: I just entered the above code into my App.xaml.cs / Application_startup routine and now German date/time formatting for C1DatePicker finally works.

这似乎正以正确的方式进行指导:我刚刚将上述代码输入到App.xaml中。cs / Application_startup例程和现在C1DatePicker的德语日期/时间格式终于可以工作了。

Got to test DataGrid right after that.

然后测试DataGrid。

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        FrameworkElement.LanguageProperty.OverrideMetadata(
            typeof(FrameworkElement),
            new FrameworkPropertyMetadata(
            System.Windows.Markup.XmlLanguage.GetLanguage(CultureInfo.CurrentUICulture.IetfLanguageTag)));
    }

Thanks!

谢谢!

Update: Tested C1DataGrid for WPF - works! This solved all the problems I had with international Date / Time settings in my Applications. Great!

更新:测试C1DataGrid用于WPF - works!这解决了我在应用程序中设置国际日期/时间的所有问题。太棒了!

#5


3  

I pretty much had the same issue.

我也有同样的问题。

I found this: http://www.codeproject.com/Articles/35159/WPF-Localization-Using-RESX-Files (might not be the original source).

我发现:http://www.codeproject.com/articles/35159 / wpf - localiz-using-resx文件(可能不是原始的源文件)。

It discusses a markup extension named "UICultureExtension" which is attached to the Language property of all framework elements that need localization (in XAML).

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

If you raise a UI language changed event, static extension managers in the background will update all registered framework elements.

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

#6


3  

Adaptive OverrideMetadata

Some form of reloading is inevitable, because changing a control's Language property doesn't make it update its text.

某种形式的重载是不可避免的,因为更改控件的语言属性不会使其更新其文本。

However, there's a way of overriding the metadata which allows you to set it once and have new controls automatically use the current culture:

然而,有一种方法可以覆盖元数据,允许您设置它一次,并让新的控件自动使用当前的文化:

FrameworkElement.LanguageProperty.OverrideMetadata(
    typeof(FrameworkElement),
    new FrameworkPropertyMetadata(
        System.Windows.Markup.XmlLanguage.Empty,
        default(PropertyChangedCallback),
        _CoerceCurrentXmlLang));

where the CoerceValueCallback is

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

By itself this isn't quite enough, because newly created controls will get the default value System.Windows.Markup.XmlLanguage.Empty without it being coerced. However, if you then set xml:lang="" in your windows' XAML, that will be coerced, and then each new control will see that it inherits a value from its parent and will coerce it. The result is that new controls added to that window will use the current language.

这本身还不够,因为新创建的控件将获得默认值System.Windows.Markup.XmlLanguage。空空如也而不被强迫。但是,如果您在windows的XAML中设置xml:lang=“”,那将被强制执行,然后每一个新的控件都将看到它从其父节点继承一个值并强制它。结果是,添加到该窗口的新控件将使用当前语言。

PS As with many things in WPF, it would be quite a bit simpler if they hadn't been so keen to keep things internal. DefaultValueFactory would be a far more elegant way of doing this.

PS:就像WPF里的很多东西一样,如果他们不是那么热衷于把事情保持在内部,事情就会简单得多。DefaultValueFactory应该是一种更为优雅的方式。

Reloading

The most extreme, but therefore reliable, way of reloading is just to create a new main window and discard the old one.

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

Almost as extreme but not quite is to arrange for the language setting to be changed only in a very simple pane of the main window with very little loaded, and that very little to be entirely databound to a viewmodel which supports forcing a property changed notification for everything.

几乎同样极端但也不完全是安排语言设置只在主窗口的一个非常简单的窗格中修改,加载非常少,并且几乎不需要对viewmodel进行完全的数据处理,该视图模型支持强制对所有内容进行属性更改通知。

Existing answers to this question have other suggestions.

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

#7


1  

It's not completely your answer, but I used this to reload the resources. But you still need to reload the windows...

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

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

#1


8  

I'm going to chime in here.

我在这里插嘴。

I successfully did this using the OverrideMetadata() method that the OP mentioned:

我成功地使用了OP提到的OverrideMetadata()方法:

var lang = System.Windows.Markup.XmlLanguage.GetLanguage(MyCultureInfo.IetfLanguageTag);
FrameworkElement.LanguageProperty.OverrideMetadata(
  typeof(FrameworkElement), 
  new FrameworkPropertyMetadata(lang)
);

But, I still found instances in my WPF in which the system culture was being applied for dates and number values. It turned out these were values in <Run> elements. It was happening because the System.Windows.Documents.Run class does not inherit from System.Windows.FrameworkElement, and so the overriding of metadata on FrameworkElement obviously had no effect.

但是,我仍然在WPF中找到了应用系统文化的日期和数字值的实例。结果显示这些值都是 <运行> 元素。因为系统。windows。document。Run类不从System.Windows继承。FrameworkElement,因此,在FrameworkElement上重写元数据显然没有效果。

System.Windows.Documents.Run inherits its Language property from System.Windows.FrameworkContentElement instead.

System.Windows.Documents。Run从System.Windows继承它的语言属性。FrameworkContentElement代替。

And so the obvious solution was to override the metadata on FrameworkContentElement in the same way. Alas, doing do threw an exception (PropertyMetadata is already registered for type System.Windows.FrameworkContentElement), and so I had to do it on the next descendant ancestor of Run instead, System.Windows.Documents.TextElement:

显而易见的解决方案是用同样的方法覆盖FrameworkContentElement上的元数据。唉,做了就抛出了一个异常(PropertyMetadata已经注册为System.Windows.FrameworkContentElement类型),所以我必须在Run的下一个子祖先system . windows . document . textelement:

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

And that sorted out all my issues.

这就解决了我所有的问题。

There are a few more sub-classes of FrameworkContentElement (listed here) which for completeness should have their metadata overridden as well.

还有一些框架内容元素的子类(此处列出),为了完整性,它们的元数据也应该被重写。

#2


5  

I'm not sure how to get around the "can't call OverrideMetadata multiple times" exception.

我不知道如何避开“不能多次调用OverrideMetadata”异常。

As a workaround, when the user changes UI cultures in your app, you could restart your app with that culture, passing in the new culture as a command line argument. Unless your users will be changing cultures often, this sounds like a reasonable solution.

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

#3


5  

I never found a way to do exactly what I asked for in the question. In my case I ended up solving it by having all my usercontrols inherit from a superclass that contained this:

我从来没有找到一种方法去做我在问题中要求的事情。在我的例子中,我最终通过让我所有的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);
    }
}

When the user changes the language I then create new instances of any usercontrols that are currently running.

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

This solved my problem. However, I would still like a way to do this "automatically" (i.e. without having to keep track of any instantiated objects).

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

#4


4  

Just my two cents: After almost going crazy when trying to implement ComponentOne WPF controls (DataGrid and C1DatePicker) with my German language assembly I stumbled upon this page.

仅仅是我的两美分:当我试图在我的德语语言程序集上实现ComponentOne WPF控件(DataGrid和C1DatePicker)时,我几乎发疯了。

This seems to be directing in the right way: I just entered the above code into my App.xaml.cs / Application_startup routine and now German date/time formatting for C1DatePicker finally works.

这似乎正以正确的方式进行指导:我刚刚将上述代码输入到App.xaml中。cs / Application_startup例程和现在C1DatePicker的德语日期/时间格式终于可以工作了。

Got to test DataGrid right after that.

然后测试DataGrid。

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        FrameworkElement.LanguageProperty.OverrideMetadata(
            typeof(FrameworkElement),
            new FrameworkPropertyMetadata(
            System.Windows.Markup.XmlLanguage.GetLanguage(CultureInfo.CurrentUICulture.IetfLanguageTag)));
    }

Thanks!

谢谢!

Update: Tested C1DataGrid for WPF - works! This solved all the problems I had with international Date / Time settings in my Applications. Great!

更新:测试C1DataGrid用于WPF - works!这解决了我在应用程序中设置国际日期/时间的所有问题。太棒了!

#5


3  

I pretty much had the same issue.

我也有同样的问题。

I found this: http://www.codeproject.com/Articles/35159/WPF-Localization-Using-RESX-Files (might not be the original source).

我发现:http://www.codeproject.com/articles/35159 / wpf - localiz-using-resx文件(可能不是原始的源文件)。

It discusses a markup extension named "UICultureExtension" which is attached to the Language property of all framework elements that need localization (in XAML).

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

If you raise a UI language changed event, static extension managers in the background will update all registered framework elements.

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

#6


3  

Adaptive OverrideMetadata

Some form of reloading is inevitable, because changing a control's Language property doesn't make it update its text.

某种形式的重载是不可避免的,因为更改控件的语言属性不会使其更新其文本。

However, there's a way of overriding the metadata which allows you to set it once and have new controls automatically use the current culture:

然而,有一种方法可以覆盖元数据,允许您设置它一次,并让新的控件自动使用当前的文化:

FrameworkElement.LanguageProperty.OverrideMetadata(
    typeof(FrameworkElement),
    new FrameworkPropertyMetadata(
        System.Windows.Markup.XmlLanguage.Empty,
        default(PropertyChangedCallback),
        _CoerceCurrentXmlLang));

where the CoerceValueCallback is

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

By itself this isn't quite enough, because newly created controls will get the default value System.Windows.Markup.XmlLanguage.Empty without it being coerced. However, if you then set xml:lang="" in your windows' XAML, that will be coerced, and then each new control will see that it inherits a value from its parent and will coerce it. The result is that new controls added to that window will use the current language.

这本身还不够,因为新创建的控件将获得默认值System.Windows.Markup.XmlLanguage。空空如也而不被强迫。但是,如果您在windows的XAML中设置xml:lang=“”,那将被强制执行,然后每一个新的控件都将看到它从其父节点继承一个值并强制它。结果是,添加到该窗口的新控件将使用当前语言。

PS As with many things in WPF, it would be quite a bit simpler if they hadn't been so keen to keep things internal. DefaultValueFactory would be a far more elegant way of doing this.

PS:就像WPF里的很多东西一样,如果他们不是那么热衷于把事情保持在内部,事情就会简单得多。DefaultValueFactory应该是一种更为优雅的方式。

Reloading

The most extreme, but therefore reliable, way of reloading is just to create a new main window and discard the old one.

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

Almost as extreme but not quite is to arrange for the language setting to be changed only in a very simple pane of the main window with very little loaded, and that very little to be entirely databound to a viewmodel which supports forcing a property changed notification for everything.

几乎同样极端但也不完全是安排语言设置只在主窗口的一个非常简单的窗格中修改,加载非常少,并且几乎不需要对viewmodel进行完全的数据处理,该视图模型支持强制对所有内容进行属性更改通知。

Existing answers to this question have other suggestions.

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

#7


1  

It's not completely your answer, but I used this to reload the resources. But you still need to reload the windows...

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

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