Windows 7 区域和语言对话框中的各种设置为 CurrentCulture 对象的属性提供值。但是,WPF 控件似乎改用 CurrentUICulture,导致完全无法尊重用户的偏好。
例如,在我的工作站上,WPF 控件似乎使用的是 en-US 的 CurrentUICulture,导致它们以美国格式 M/d/yyyy 显示日期,而不是在区域和语言对话框中指定的澳大利亚格式。
在数据绑定中明确指定 en-AU 区域性会导致相关控件使用默认的澳大利亚格式,但它会继续忽略用户指定的格式。这很奇怪;进入应用程序我验证了 DateTimeFormatInfo.CurrentInfo == Thread.CurrentThread.CurrentCulture.DateTimeFormat (相同的对象)和 DateTimeFormatInfo.CurrentInfo.ShortDatePattern == "yyyy-MM-dd" (我设置的一个值,以便我可以确定用户偏好或正在选择默认值)。一切都如预期的那样,所以从表面上看,最大的问题是如何说服 WPF 控件和数据绑定使用 CurrentCulture 而不是 CurrentUICulture。
我们应该如何让 WPF 应用程序尊重区域和语言设置?
基于 Sphinxx 的回答,我覆盖了 Binding 类的两个构造函数,以提供与标准标记的更完整兼容性。
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;
}
}
}
进一步的实验表明,您可以使用 x:Static 在标记中引用 System.Globalization.CultureInfo.CurrentCulture。这在运行时是完全成功的,但在设计时是灾难,因为绑定编辑器不断删除它。更好的解决方案是一个帮助类来遍历窗口的 DOM 并修复它找到的每个 Binding 的 ConverterCulture。
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);
}
}
}
}