7

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);
        }
    }

  }
}
4

2 に答える 2

8

この SO 投稿(WPF/Silverlight) には、この記事(WPF のみ)へのリンクがあり、CurrentCulture をアプリケーションのデフォルトとして使用する方法が説明されています。それはあなたの問題を解決しますか?

編集:

現在の言語のデフォルト設定ではなく、「地域と言語」のカスタム設定を使用するバインディングを作成するには、さらにいくつかのトリックが必要です。この投稿ConverterCultureでは、すべてのバインディングも明示的に に設定する必要があると結論付けていCultureInfo.CurrentCultureます。ここにいくつかの DateTime テストがあります:

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

カスタム バインディング クラス:

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

ノルウェーのマシンでは、最終的に次のようになります。

WPF 日時バインディング

于 2013-01-04T19:16:15.010 に答える