WPF 运行时区域设置更改,重新评估 ValueConverters UI
WPF Runtime Locale Change, reevaluate ValueConverters UI
在大型 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-time culture change and IValueConverter in a binding
(我没有为每个字段手动提高 propertychanged 的选项)
这是我们的解决方案。我希望我理解你想要改变的问题,例如 DateTime
?
Converter
是一个简单的 IValueConverter
,它将值转换为当前语言。 Translator
是静态的 class,它包含(例如)CurrentLanguage
(en-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;
}
}
行为
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>
预览
作为一个选项 - 您可以围绕 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;
}
}
这样使用:
<TextBlock Text="{my:LocBinding Binding={Binding ActivityCode, Converter={StaticResource myConverter}}}" />
您可以提供任何绑定(包括 MultiBinding
)并在可以应用绑定的地方使用任何 属性。
如果您认为即使这样也过于冗长 - 您可以用不同的方式包装绑定 - 通过镜像您在标记扩展中需要的 Binding class 的所有属性并将它们转发到基础绑定。在这种情况下,您将不得不编写更多的代码,并且您将需要为 Binding 和 MultiBinding 提供单独的 classes(以防您也需要 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;
}
}
然后用法简化为:
<TextBlock Text="{my:LocBinding ActivityCode, Converter={StaticResource myConverter}}" />
您可以根据需要添加更多属性(如 Mode
等)。
在大型 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-time culture change and IValueConverter in a binding (我没有为每个字段手动提高 propertychanged 的选项)
这是我们的解决方案。我希望我理解你想要改变的问题,例如 DateTime
?
Converter
是一个简单的 IValueConverter
,它将值转换为当前语言。 Translator
是静态的 class,它包含(例如)CurrentLanguage
(en-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;
}
}
行为
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>
预览
作为一个选项 - 您可以围绕 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;
}
}
这样使用:
<TextBlock Text="{my:LocBinding Binding={Binding ActivityCode, Converter={StaticResource myConverter}}}" />
您可以提供任何绑定(包括 MultiBinding
)并在可以应用绑定的地方使用任何 属性。
如果您认为即使这样也过于冗长 - 您可以用不同的方式包装绑定 - 通过镜像您在标记扩展中需要的 Binding class 的所有属性并将它们转发到基础绑定。在这种情况下,您将不得不编写更多的代码,并且您将需要为 Binding 和 MultiBinding 提供单独的 classes(以防您也需要 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;
}
}
然后用法简化为:
<TextBlock Text="{my:LocBinding ActivityCode, Converter={StaticResource myConverter}}" />
您可以根据需要添加更多属性(如 Mode
等)。