WPF Runtime Locale Change, overestimate ValueConverters interface

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

How can I make WPF update all converted bindings in system-wide applications?

EDIT: We were currently experimenting by creating ValueConverters MultiValueConverters and adding a locale as an extra value. In this way, the values ​​of the value values ​​are changed, and the result is updated. But it is bulky 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> 

RELATED : Changing the runtime and IValueConverter in the binding (I have no way to manually change the property for each field)

+8
c # data-binding converter wpf localization
source share
2 answers

As an option, you can create a shell markup extension around Binding , for example:

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

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

You can provide any binding (including MultiBinding ) and use any property in which the binding can be applied.

If you think that even this is too verbose - you can bind the binding in a different way - by mirroring all the properties of the Binding class that you need in the markup extension and redirecting them to the base binding. In this case, you will need to write a little more code, and you will need separate classes for Binding and MultiBinding (if necessary, MultiBinding ). The best way is to inherit from Binding and redefine ProvideValue , but it is not virtual, so this cannot be done, and I have not found other methods that you can override to achieve the result. Here is a sketch with two snap properties:

 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 use is simplified:

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

If necessary, you can add additional properties (for example, Mode , etc.).

+5
source share

This is our decision. I hope I understand your problem that you want to change, e.g. DateTime ?

Converter is a simple IValueConverter that converts a value to the current language. Translator is a static class that contains (for example) CurrentLanguage ( en-en / de-de ) as a string .

To update the bindings, if the language has been changed, Behavior is required. We need this implementation 3-4 times in the hole program, because it is only intended to form a DateTime . All other texts are stored in a dynamic resource.

But I think Behavior is right for your needs.

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

preview

+3
source share

All Articles