Assorted settings in the Windows 7 Region and Language dialog supply values to the properties of the CurrentCulture object. However, WPF controls seem to use CurrentUICulture instead, resulting in a total failure to respect the user's preferences.
On my workstation, for example, WPF controls seem to use CurrentUICulture which is en-US, causing them to display dates with the American format M/d/yyyy rather than the Australia format specified in the Region and Language dialog.
Explicitly specifying a culture of en-AU in a databinding causes the control in question to to use default Australian formats, but it continues to ignore the user specified formats. This is odd; stepping into the app I verified that DateTimeFormatInfo.CurrentInfo == Thread.CurrentThread.CurrentCulture.DateTimeFormat (same object) and DateTimeFormatInfo.CurrentInfo.ShortDatePattern == "yyyy-MM-dd" (a value I set so I could determine whether user preferences or defaults were being picked up). Everything was as expected, so on the face of things the big question is how to persuade WPF controls and databindings to use CurrentCulture rather than CurrentUICulture.
How are we supposed to get WPF apps to respect the Region and Language settings?
Building on Sphinxx's answer, I overrode both constructors of the Binding class 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 reveals that you can use x:Static to reference System.Globalization.CultureInfo.CurrentCulture in markup. This is a complete success at run-time but a disaster at design-time because the binding editor keeps removing it. A better solution is a helper class to traverse the DOM of a window and fix up the ConverterCulture of every Binding it finds.
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);
}
}
}
}
This SO post (WPF/Silverlight) has a link to this article (WPF only), explaining how to use CurrentCulture as the default for your application. Does that solve your problem?
EDIT:
Making bindings use the custom settings from "Region and Language" instead of the current language's default settings requires some more trickery. This post concludes that every binding's ConverterCulture
also has to be explicitly set to CultureInfo.CurrentCulture
. Here are some DateTime tests:
<!-- Culture-aware(?) bindings -->
<StackPanel DataContext="{Binding Source={x:Static sys:DateTime.Now}}" >
<!-- WPF's 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 (d.M.YY) -->
<TextBlock Text="{Binding Path=., ConverterCulture={x:Static sysglb:CultureInfo.CurrentCulture}}" />
<!-- Hiding ConverterCulture initialization in our own custom Binding class as suggested here:
https://stackoverflow.com/questions/5831455/use-real-cultureinfo-currentculture-in-wpf-binding-not-cultureinfo-from-ietfl#5937477 -->
<TextBlock Text="{local:CultureAwareBinding Path=.}" />
</StackPanel>
The custom binding class:
public class CultureAwareBinding : Binding
{
public CultureAwareBinding()
{
this.ConverterCulture = System.Globalization.CultureInfo.CurrentCulture;
}
}
It all ends up looking like this on a norwegian machine: