WPF运行时区域设置更改,重新评估ValueConverters UI [英] WPF Runtime Locale Change, reevaluate ValueConverters UI

查看:126
本文介绍了WPF运行时区域设置更改,重新评估ValueConverters UI的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在大型WPF应用程序中,我们可以在运行时更改语言.我们使用 WPF本地化扩展和resx文件进行本地化,并且效果很好,除了UI中使用的转换器.如果绑定中的ValueConverter是特定于区域性的,则生成的文本不会随着语言的更改而更新.

In a large WPF application, we have the possibility to change the language at runtime. We use WPF Localize Extension and resx-files for localization and it works great, except for converters used in UI. If in a binding a ValueConverter is culture-specific, the resulting text is not updated on the language change.

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

How can I make WPF update all converted bindings application-wide?

目前,我们通过制作ValueConverters MultiValueConverters并将区域设置添加为附加值进行了实验.这样,值源值就会更改,并且结果会更新.但这麻烦又丑陋.

At the moment we have experimented by making the ValueConverters MultiValueConverters and adding the locale as an extra value. This way the value source values change, and the result is updated. But this is cumbersome and ugly.

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

相关: 运行时文化更改和绑定中的IValueConverter (我没有选择手动更改每个字段的属性)

Related: Run-time culture change and IValueConverter in a binding (I don't have the option to raise propertychanged for every field manually)

推荐答案

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

As an option - you can create wrapper markup extension around Binding, like this:

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)并使用可以应用绑定的任何属性.

You can provide any binding (including MultiBinding) and use any property where binding can be applied.

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

If you think that even this is too verbose - you can wrap binding in a different way - by mirroring all properties of Binding class you need on your markup extension and forward them to underlying binding. In this case you will have to write a bit more code, and you will need to have separate classes for Binding and MultiBinding (in case you need MultiBinding too). Best way would be to inherit from Binding and override it's ProvideValue, but it's not virtual so not possible to do that, and I didn't find any other methods you can override to achieve the result. Here is a sketch with just 2 properties of binding:

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等).

You can add more properties (like Mode and so on) as needed.

这篇关于WPF运行时区域设置更改,重新评估ValueConverters UI的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆