How to set and change the culture in WPF
Asked Answered
R

8

59

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:

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.

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

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

See for example StringFormat Localization issues in wpf.

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.

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.

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

(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.)

Retroflex answered 28/10, 2010 at 8:48 Comment(1)
I never found a way to do what I asked for in the question.Directive
R
8

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:

/// <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).

Retroflex answered 16/11, 2010 at 13:30 Comment(3)
Hi, I've the same problem. I try to change InputLanguage by 'InputLanguageManager.SetInputLanguage(_targetKeyboardWindow, CultureInfo.CreateSpecificCulture("ru"));' and then I reload my View and set up the Language in constructor. But CultureInfo.CurrentCulture.IetfLanguageTag. What I need to do, to change the language at runtime?Nino
@T.J.Kjaer - vote up for coming back with an answer. So many people bail on their own question.Milkman
This seems to be Deprecated. Is there an update to it?Letendre
U
13

I'm going to chime in here.

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

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.

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

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.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.

Urbanna answered 6/2, 2014 at 12:59 Comment(1)
Thanks, though you don't quite answer the OP's question, you certainly answered mine. I was running into the crash when setting it for FrameworkContentElement). You can find code which takes care of the other sub-classes at gist.github.com/adrianratnapala/6775b8de586317812ad5Springer
R
8

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:

/// <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).

Retroflex answered 16/11, 2010 at 13:30 Comment(3)
Hi, I've the same problem. I try to change InputLanguage by 'InputLanguageManager.SetInputLanguage(_targetKeyboardWindow, CultureInfo.CreateSpecificCulture("ru"));' and then I reload my View and set up the Language in constructor. But CultureInfo.CurrentCulture.IetfLanguageTag. What I need to do, to change the language at runtime?Nino
@T.J.Kjaer - vote up for coming back with an answer. So many people bail on their own question.Milkman
This seems to be Deprecated. Is there an update to it?Letendre
K
6

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

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.

Kozak answered 8/11, 2010 at 19:55 Comment(1)
For future visitors: This is the solution to the "can't call OverrideMetadata multiple times" exception: SO QuestionToken
M
5

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.

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.

Got to test DataGrid right after that.

    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!

Monazite answered 11/2, 2013 at 17:19 Comment(0)
C
4

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

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.

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.

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.

Existing answers to this question have other suggestions.

Czardom answered 28/1, 2016 at 15:51 Comment(5)
the problem is, that this takes the culture as it is predefined, it ignores all custom settings, that are used when using .ToString(CultureInfo.CurrentCulture)Woodenware
@Matus, I'm not sure that I understand you. It seems to me that your complaint is that there is no static method System.Windows.Markup.XmlLanguage.GetLanguage(CultureInfo), but that's a fundamental limitation of WPF and random StackOverflow users can't do anything to change it.Czardom
what I am trying to say is, that if you have english culture, but change something, e.g. decimal separator, this solution would still format the numbers with the default, which is a dot for en-USWoodenware
That is true, but I don't see why you're mentioning it as a comment on this answer. It applies equally to all of the other answers, because it's a flaw in WPF itself. If you want to know how to display things formatted according to the true CurrentCultureUI then post a new question (assuming it hasn't already been asked) and I will give the best answer I know, but I will confess now that it's not very elegant.Czardom
I agree, that it applies to almost all the answers, I already found a semi-elegant solution here: codeproject.com/Tips/1004834/…Woodenware
P
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).

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

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

Prehistoric answered 24/7, 2013 at 8:42 Comment(0)
F
2

I use the merged resource dictionary/dynamic resource appproach to change my strings. But for correct automatic date/time/other regional formatting I am setting the language on Application.Current.MainWindow.Language, it gets inherited by the children and updates automatically. At least on 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);

Fresco answered 17/5, 2021 at 7:15 Comment(1)
work also on .Net Core 3.1Kermanshah
P
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);
        }
Piliferous answered 9/11, 2010 at 8:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.