CurrentUICulture ignores region and language settings

The assorted options in the Windows 7 Region and Language dialog box correspond to the properties of the CurrentCulture object. However, WPF controls seem to use CurrentUICulture instead, which leads to a general rejection of user preferences.

On my workstation, for example, WPF controls seem to use CurrentUICulture, which is en-US, forcing them to display dates in the American format M / d / yyyy, rather than in the Australian format specified in the Region and Language dialog box .

Explicitly specifying an en-AU culture in data binding forces this control to use Australian default formats, but it continues to ignore user-specified formats. This is strange; entering the application, I confirmed that DateTimeFormatInfo.CurrentInfo == Thread.CurrentThread.CurrentCulture.DateTimeFormat (the same object) and DateTimeFormatInfo.CurrentInfo.ShortDatePattern == "yyyy-MM-dd" (the value that I set to determine or were raised by default). Everything was as expected, so at first glance, the big question is how to convince WPF controls and data bindings to use CurrentCulture rather than CurrentUICulture.

How should we receive WPF applications to comply with the region and language settings?


Based on Sphinxx's answer, I circumvented both Binding class constructors to provide more complete compatibility with standard markup.

using System.Globalization; using System.Windows.Data; namespace ScriptedRoutePlayback { public class Bind : Binding { public Bind() { ConverterCulture = CultureInfo.CurrentCulture; } public Bind(string path) : base(path) { ConverterCulture = CultureInfo.CurrentCulture; } } } 

Further experimentation shows that you can use x: Static to reference System.Globalization.CultureInfo.CurrentCulture in the markup. This is a complete success at runtime, but a disaster at development time because the binding editor continues to delete it. The best solution is a helper class that traverses the DOM windows and captures the conversion culture of every Binding found.

 using System; using System.Windows; using System.Windows.Data; using System.ComponentModel; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; namespace ScriptedRoutePlayback { public static class DependencyHelper { static Attribute[] __attrsForDP = new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues | PropertyFilterOptions.UnsetValues | PropertyFilterOptions.Valid) }; public static IList<DependencyProperty> GetProperties(Object element, bool isAttached = false) { if (element == null) throw new ArgumentNullException("element"); List<DependencyProperty> properties = new List<DependencyProperty>(); foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(element, __attrsForDP)) { DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(pd); if (dpd != null && dpd.IsAttached == isAttached) { properties.Add(dpd.DependencyProperty); } } return properties; } public static IEnumerable<Binding> EnumerateBindings(DependencyObject dependencyObject) { if (dependencyObject == null) throw new ArgumentNullException("dependencyObject"); LocalValueEnumerator lve = dependencyObject.GetLocalValueEnumerator(); while (lve.MoveNext()) { LocalValueEntry entry = lve.Current; if (BindingOperations.IsDataBound(dependencyObject, entry.Property)) { Binding binding = (entry.Value as BindingExpression).ParentBinding; yield return binding; } } } /// <summary> /// Use in the constructor of each Window, after initialisation. /// Pass "this" as the dependency object and omit other parameters to have /// all the bindings in the window updated to respect user customisations /// of regional settings. If you want a specific culture then you can pass /// values to recurse and cultureInfo. Setting recurse to false allows you /// to update the bindings on a single dependency object. /// </summary> /// <param name="dependencyObject">Root dependency object for binding change treewalk</param> /// <param name="recurse">A value of true causes processing of child dependency objects</param> /// <param name="cultureInfo">Defaults to user customisations of regional settings</param> public static void FixBindingCultures(DependencyObject dependencyObject, bool recurse = true, CultureInfo cultureInfo = null) { if (dependencyObject == null) throw new ArgumentNullException("dependencyObject"); try { foreach (object child in LogicalTreeHelper.GetChildren(dependencyObject)) { if (child is DependencyObject) { //may have bound properties DependencyObject childDependencyObject = child as DependencyObject; var dProps = DependencyHelper.GetProperties(childDependencyObject); foreach (DependencyProperty dependencyProperty in dProps) RegenerateBinding(childDependencyObject, dependencyProperty, cultureInfo); //may have children if (recurse) FixBindingCultures(childDependencyObject, recurse, cultureInfo); } } } catch (Exception ex) { Trace.TraceError(ex.Message); } } public static void RegenerateBinding(DependencyObject dependencyObject, DependencyProperty dependencyProperty, CultureInfo cultureInfo = null) { Binding oldBinding = BindingOperations.GetBinding(dependencyObject, dependencyProperty); if (oldBinding != null) try { //Bindings cannot be changed after they are used. //But they can be regenerated with changes. Binding newBinding = new Binding() { Converter = oldBinding.Converter, ConverterCulture = cultureInfo ?? CultureInfo.CurrentCulture, ConverterParameter = oldBinding.ConverterParameter, FallbackValue = oldBinding.FallbackValue, IsAsync = oldBinding.IsAsync, Mode = oldBinding.Mode, NotifyOnSourceUpdated = oldBinding.NotifyOnSourceUpdated, NotifyOnTargetUpdated = oldBinding.NotifyOnValidationError, Path = oldBinding.Path, StringFormat = oldBinding.StringFormat, TargetNullValue = oldBinding.TargetNullValue, UpdateSourceExceptionFilter = oldBinding.UpdateSourceExceptionFilter, UpdateSourceTrigger = oldBinding.UpdateSourceTrigger, ValidatesOnDataErrors = oldBinding.ValidatesOnDataErrors, ValidatesOnExceptions = oldBinding.ValidatesOnExceptions, XPath = oldBinding.XPath }; //set only one of ElementName, RelativeSource, Source if (oldBinding.ElementName != null) newBinding.ElementName = oldBinding.ElementName; else if (oldBinding.RelativeSource != null) newBinding.Source = oldBinding.Source; else newBinding.RelativeSource = oldBinding.RelativeSource; BindingOperations.ClearBinding(dependencyObject, dependencyProperty); BindingOperations.SetBinding(dependencyObject, dependencyProperty, newBinding); } catch (Exception ex) { Trace.TraceError(ex.Message); } } } } 
+4
source share
2 answers

This SO (WPF / Silverlight) post has a link in this article (WPF only) explaining how to use CurrentCulture as the default value for your application. Does your problem solve?

EDIT:

Creating bindings uses the user settings from "Region and language" instead of the current default settings for the language, it requires a few more tricks. This post concludes that for each ConverterCulture binding, CultureInfo.CurrentCulture must also be explicitly set. Here are some DateTime tests:

 <!-- Culture-aware(?) bindings --> <StackPanel DataContext="{Binding Source={x:Static sys:DateTime.Now}}" > <!-- WPF default en-US formatting (regardless of any culture/language settings) --> <TextBlock Text="{Binding Path=.}" /> <!-- *Default* norwegian settings (dd.MM.YYY) --> <TextBlock Text="{Binding Path=., ConverterCulture=nb-NO}" /> <!-- Norwegian settings from the "Region and Languague" dialog (dMYY) --> <TextBlock Text="{Binding Path=., ConverterCulture={x:Static sysglb:CultureInfo.CurrentCulture}}" /> <!-- Hiding ConverterCulture initialization in our own custom Binding class as suggested here: /questions/173811/use-real-cultureinfocurrentculture-in-wpf-binding-not-cultureinfo-from-ietflanguagetag#5937477 --> <TextBlock Text="{local:CultureAwareBinding Path=.}" /> </StackPanel> 

Custom Binding Class:

 public class CultureAwareBinding : Binding { public CultureAwareBinding() { this.ConverterCulture = System.Globalization.CultureInfo.CurrentCulture; } } 

It all looks like this on a Norwegian computer:

WPF DateTime Bindings

+7
source

There is one very dirty way to do this in WPF, but as far as I can find, this is best because it works without any other additional code or has specific cultural bindings. The only thing you need to do is call the SetFrameworkElementLanguageDirty method (below in the answer) when the application starts, or even better in the application constructor.

The method comments are self-explanatory, but in short the method overrides the default LanguageProperty FrameworkElement metadata with CurrentCulture, including modifying a specific user from windows, if any. The smaller / dirty part is that it uses reflection to set the private field of the XmlLanguage object.

  /// <summary> /// Sets the default language for all FrameworkElements in the application to the user system culture (rather than /// the default "en-US"). /// The WPF binding will use that default language when converting types to their string representations (DateTime, /// decimal...). /// </summary> public static void SetFrameworkElementLanguageDirty() { // Note that the language you get from "XmlLanguage.GetLanguage(currentCulture.IetfLanguageTag)" // doesn't include specific user customizations, for example of date and time formats (Windows date and time settings). var xmlLanguage = XmlLanguage.GetLanguage(Thread.CurrentThread.CurrentCulture.IetfLanguageTag); SetPrivateField(xmlLanguage, "_equivalentCulture", Thread.CurrentThread.CurrentCulture); FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(xmlLanguage)); } 

The SetPrivateField method might look like this.

  private static void SetPrivateField(object obj, string name, object value) { var privateField = obj.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic); if (privateField == null) throw new ArgumentException($"{obj.GetType()} doesn't have a private field called '{name}'."); privateField.SetValue(obj, value); } 
+1
source

All Articles