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" (ユーザー設定かどうかを判断できるように設定した値) であることを確認しましたまたはデフォルトが選択されていました)。すべてが期待どおりだったので、大きな問題は、CurrentUICulture ではなく CurrentCulture を使用するように WPF コントロールとデータバインディングを説得する方法です。
地域と言語の設定を尊重するように 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);
}
}
}
}