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