WPF Runtime Locale Change, reevaluate ValueConverters UI

后端 未结 2 1166
花落未央
花落未央 2021-01-02 10:35

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

2条回答
  •  隐瞒了意图╮
    2021-01-02 11:07

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

    Use like this:

    
    

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

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

    Then usage is simplified to just:

    
    

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

提交回复
热议问题