WPF Runtime Locale Change, reevaluate ValueConverters UI

后端 未结 2 1167
花落未央
花落未央 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 10:59

    This is our solution. I hope I understood your problem that you want to change for example DateTime?

    The Converter is a simple IValueConverter which convert the value to the current Language. Translator is a static class which holds (for example) the CurrentLanguage (en-en / de-de) as string.

    The Behavior is needed to update the Bindings if the language has changed. We only need this implementation 3-4 times in the hole program, because it is only for the DateTime formating. All other texts are hold in a dynamic Resource..

    But I think for your needs the Behavior is the right one.

    Converter

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

    Behavior

    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>
    

    Preview

    0 讨论(0)
  • 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<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;  
        }
    }
    

    Use like this:

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

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

    Then usage is simplified to just:

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

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

    0 讨论(0)
提交回复
热议问题