WPF运行时区域设置更改,重新评估ValueConverters UI

Bjo*_*orn 8 c# data-binding wpf localization converter

在大型WPF应用程序中,我们可以在运行时更改语言.我们使用WPF Localize Extension和resx文件进行本地化,除了UI中使用的转换器外,它工作得很好.如果在绑定中ValueConverter是特定于文化的,则不会在语言更改时更新生成的文本.

如何让WPF在应用程序范围内更新所有转换后的绑定?

编辑:目前我们已经通过制作ValueConverters MultiValueConverters并将区域设置添加为额外值进行了实验.这样,值源值会更改,并且结果会更新.但这很麻烦而且很难看.

<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>
Run Code Online (Sandbox Code Playgroud)

相关: 绑定中的运行时文化更改和IValueConverter (我没有手动为每个字段提高propertychanged的选项)

Dom*_*nas 6

这是我们的解决方案。我希望我理解你的问题,例如你想改变DateTime

Converter是一个简单的IValueConverter将值转换为当前语言的方法。Translator是一个静态类,它保存(例如)CurrentLanguageen-en / de-de)为string

Behavior如果语言已更改,则需要更新绑定。我们在hole程序中只需要这个实现3-4次,因为它只是为了DateTime格式化。所有其他文本都保存在动态资源中。

但我认为对于您的需求,这Behavior是正确的。

转换器

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

行为

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;
    }
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

预览

预览


Evk*_*Evk 5

作为一个选项 - 您可以创建包装器标记扩展Binding,如下所示:

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;  
    }
}
Run Code Online (Sandbox Code Playgroud)

使用这样:

<TextBlock Text="{my:LocBinding Binding={Binding ActivityCode, Converter={StaticResource myConverter}}}" />
Run Code Online (Sandbox Code Playgroud)

您可以提供任何绑定(包括MultiBinding)并使用可以应用绑定的任何属性.

如果您认为即使这样过于冗长 - 您可以通过镜像标记扩展所需的Binding类的所有属性并将它们转发到底层绑定来以不同的方式包装绑定.在这种情况下,您将需要编写更多代码,并且您需要为Binding和MultiBinding创建单独的类(如果您也需要MultiBinding).最好的方法是继承Binding和覆盖它ProvideValue,但它不是虚拟的,所以不可能这样做,我没有找到任何其他方法可以覆盖以实现结果.这是一个只有2个绑定属性的草图:

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;  
    }
}
Run Code Online (Sandbox Code Playgroud)

然后使用简化为:

<TextBlock Text="{my:LocBinding ActivityCode, Converter={StaticResource myConverter}}" />
Run Code Online (Sandbox Code Playgroud)

您可以Mode根据需要添加更多属性(如等).