【问题标题】:WPF Runtime Locale Change, reevaluate ValueConverters UIWPF 运行时区域设置更改,重新评估 ValueConverters UI
【发布时间】:2017-10-13 22:53:50
【问题描述】:

在大型 WPF 应用程序中,我们可以在运行时更改语言。我们使用WPF Localize Extension 和 resx-files 进行本地化,效果很好,除了 UI 中使用的转换器。如果在绑定中 ValueConverter 是特定于文化的,则生成的文本不会随着语言的变化而更新。

如何让 WPF 在应用程序范围内更新所有已转换的绑定?

编辑:目前我们已经通过制作 ValueConverters MultiValueConverters 并将语言环境添加为额外值进行了实验。这样,值源值会发生变化,结果也会更新。但这既麻烦又丑陋。

<MultiBinding Converter="{StaticResource _codeMultiConverter}" ConverterParameter="ZSLOOPAKT">
  <Binding Path="ActivityCode" />
  <Binding Source="{x:Static lex:LocalizeDictionary.Instance}" Path="Culture" />
  <Binding Source="{x:Static RIEnums:CodeTypeInfo+CodeDisplayMode.Both}" />
</MultiBinding>

相关: Run-time culture change and IValueConverter in a binding (我没有为每个字段手动提升 propertychanged 的​​选项)

【问题讨论】:

    标签: c# wpf data-binding localization converter


    【解决方案1】:

    这是我们的解决方案。我希望我能理解您想要更改的问题,例如 DateTime?

    Converter 是一个简单的IValueConverter,它将值转换为当前语言。 Translator 是一个静态类,它持有(例如)CurrentLanguage (en-en / de-de) 为 string

    如果语言已更改,则需要 Behavior 来更新绑定。在hole程序中我们只需要这个实现3-4次,因为它只针对DateTime格式。所有其他文本都保存在动态资源中..

    但我认为Behavior 是适合您的需求。

    转换器

    public class CultureConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value != null)
            {
                DateTime dateTime;
                if(DateTime.TryParse(value.ToString(), out dateTime))
                {
                    if(parameter != null)
                    {
                        return dateTime.ToString(parameter.ToString(), new CultureInfo(Translator.CurrentLanguage));
                    }
                    return dateTime.ToString(new CultureInfo(Translator.CurrentLanguage));
                }
                return null;
            }
            return null;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }
    

    行为

    public class CultureConverter : Behavior<FrameworkElement>
    {
        private FrameworkElement _HostingControl;
        private DependencyProperty _HostingControlDependencyProperty;
    
        protected override void OnAttached()
        {
            base.OnAttached();
    
            _HostingControl = AssociatedObject;
    
            _InitHostingControl();
            Translator.LanguageChanged += Translator_LanguageChanged;
        }
    
        protected override void OnDetaching()
        {
            Translator.LanguageChanged -= Translator_LanguageChanged;
    
            base.OnDetaching();
        }
    
        private void Translator_LanguageChanged(string languageCode)
        {
            if(_HostingControlDependencyProperty != null)
                _HostingControl.GetBindingExpression(_HostingControlDependencyProperty).UpdateTarget();
        }
    
        private void _InitHostingControl()
        {
            if(_HostingControl is TextBlock)
            {
                _HostingControlDependencyProperty = TextBlock.TextProperty;
            }
            else if (typeof(TextBox) == _HostingControl.GetType())
                _HostingControlDependencyProperty = TextBox.TextProperty;
        }
    

    XAML

    <Window.Resources>
        <XamlConverter:CultureConverter x:Key="CultureConverter"/>
    <Window.Resources>
    
    
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock 
                Text="{Binding CreatedOn, ConverterParameter=f, Converter={StaticResource CultureConverter}, UpdateSourceTrigger=PropertyChanged}">
                <i:Interaction.Behaviors>
                    <Behaviors:CultureConverter/>
                </i:Interaction.Behaviors>
            </TextBlock>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
    

    预览

    【讨论】:

      【解决方案2】:

      作为一个选项 - 您可以围绕 Binding 创建包装标记扩展,如下所示:

      public class LocBindingExtension : MarkupExtension {
          public BindingBase Binding { get; set; }
      
          public override object ProvideValue(IServiceProvider serviceProvider) {
              if (Binding == null)
                  return null;
      
              // Binding is by itself MarkupExtension
              // Call its ProvideValue
              var expression = Binding.ProvideValue(serviceProvider) as BindingExpressionBase;
              if (expression != null) {                
                  // if got expression - create weak reference
                  // you don't want for this to leak memory by preventing binding from GC
                  var wr = new WeakReference<BindingExpressionBase>(expression);
                  PropertyChangedEventHandler handler = null;
                  handler = (o, e) => {                    
                      if (e.PropertyName == nameof(LocalizeDictionary.Instance.Culture)) {
                          BindingExpressionBase target;
                          // when culture changed and our binding expression is still alive - update target
                          if (wr.TryGetTarget(out target))
                              target.UpdateTarget();
                          else
                              // if dead - unsubsribe
                              LocalizeDictionary.Instance.PropertyChanged -= handler;
                      }
      
                  };
                  LocalizeDictionary.Instance.PropertyChanged += handler;
                  return expression;
              }
              // return self if there is no binding target (if we use extension inside a template for example)
              return this;  
          }
      }
      

      这样使用:

      <TextBlock Text="{my:LocBinding Binding={Binding ActivityCode, Converter={StaticResource myConverter}}}" />
      

      您可以提供任何绑定(包括MultiBinding)并使用可以应用绑定的任何属性。

      如果您认为即使这样也过于冗长 - 您可以以不同的方式包装绑定 - 通过在标记扩展上镜像您需要的 Binding 类的所有属性并将它们转发到底层绑定。在这种情况下,您将不得不编写更多代码,并且您需要为 Binding 和 MultiBinding 设置单独的类(以防您也需要 MultiBinding)。最好的方法是从Binding 继承并覆盖它的ProvideValue,但它不是虚拟的,所以不可能这样做,而且我没有找到任何其他可以覆盖的方法来实现结果。这是一个只有 2 个绑定属性的草图:

      public class LocBindingExtension : MarkupExtension {
          private readonly Binding _inner;
          public LocBindingExtension() {
              _inner = new Binding();
          }
      
          public LocBindingExtension(PropertyPath path) {
              _inner = new Binding();
              this.Path = path;
          }
      
          public IValueConverter Converter
          {
              get { return _inner.Converter; }
              set { _inner.Converter = value; }
          }
      
          public PropertyPath Path
          {
              get { return _inner.Path; }
              set { _inner.Path = value; }
          }
      
          public override object ProvideValue(IServiceProvider serviceProvider) {            
              // Binding is by itself MarkupExtension
              // Call its ProvideValue
              var expression = _inner.ProvideValue(serviceProvider) as BindingExpressionBase;
              if (expression != null) {                
                  // if got expression - create weak reference
                  // you don't want for this to leak memory by preventing binding from GC
                  var wr = new WeakReference<BindingExpressionBase>(expression);
                  PropertyChangedEventHandler handler = null;
                  handler = (o, e) => {                    
                      if (e.PropertyName == nameof(LocalizeDictionary.Instance.Culture)) {
                          BindingExpressionBase target;
                          // when culture changed and our binding expression is still alive - update target
                          if (wr.TryGetTarget(out target))
                              target.UpdateTarget();
                          else
                              // if dead - unsubsribe
                              LocalizeDictionary.Instance.PropertyChanged -= handler;
                      }
      
                  };
                  LocalizeDictionary.Instance.PropertyChanged += handler;
                  return expression;
              }
              // return self if there is no binding target (if we use extension inside a template for example)
              return this;  
          }
      }
      

      然后用法被简化为:

      <TextBlock Text="{my:LocBinding ActivityCode, Converter={StaticResource myConverter}}" />
      

      您可以根据需要添加更多属性(如Mode 等)。

      【讨论】:

      • 这也是一个有趣的解决方案!
      • 我认为这是一个合适的解决方案。有没有可能用一个绑定而不是一个绑定中的绑定来做到这一点?
      • @Bjorn 我已经用另一种包装绑定的方式更新了答案。
      • @Bjorn 我对答案进行了重要更新,请注意return this,以防expression is null。这是自定义扩展在模板中正常工作所必需的(例如DataTemplate)。
      • 我可以确认这个解决方案已经解决了我们的问题。谢谢!
      猜你喜欢
      • 1970-01-01
      • 2017-04-07
      • 1970-01-01
      • 2011-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多