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

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

问题描述

在大型 WPF 应用程序中,我们可以在运行时更改语言.我们使用 WPF Localize Extension 和 resx-files 进行本地化,它工作得很好,除了 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(我没有为每个字段手动提高 propertychanged 的​​选项)

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天全站免登陆