問題描述
在大型 WPF 應用程序中,我們可以在運行時更改語言.我們使用 WPF Localize Extension 和 resx-files 進行本地化,它工作得很好,除了 UI 中使用的轉換器.如果在綁定中 ValueConverter 是特定于文化的,則生成的文本不會隨著語言的變化而更新.
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, except for converters used in UI. If in a binding a ValueConverter is culture-specific, the resulting text is not updated on the language change.
如何讓 WPF 在應用程序范圍內更新所有轉換后的綁定?
How can I make WPF update all converted bindings application-wide?
目前我們已經通過制作 ValueConverters MultiValueConverters 并將語言環境添加為額外值進行了實驗.這樣,值源值會發生變化,結果也會更新.但這既麻煩又丑陋.
At the moment we have experimented by making the ValueConverters MultiValueConverters and adding the locale as an extra value. This way the value source values change, and the result is updated. But this is cumbersome 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>
相關:運行時文化變化和綁定中的 IValueConverter(我沒有為每個字段手動提高 propertychanged 的??選項)
Related: Run-time culture change and IValueConverter in a binding (I don't have the option to raise propertychanged for every field manually)
推薦答案
作為一個選項 - 您可以圍繞 Binding
創建包裝標記擴展,如下所示:
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;
}
}
這樣使用:
<TextBlock Text="{my:LocBinding Binding={Binding ActivityCode, Converter={StaticResource myConverter}}}" />
您可以提供任何綁定(包括 MultiBinding
)并使用可以應用綁定的任何屬性.
You can provide any binding (including MultiBinding
) and use any property where binding can be applied.
如果您認為即使這樣也過于冗長 - 您可以以不同的方式包裝綁定 - 通過在標記擴展上鏡像您需要的 Binding 類的所有屬性并將它們轉發到底層綁定.在這種情況下,您將不得不編寫更多代碼,并且您需要為 Binding 和 MultiBinding 設置單獨的類(以防您也需要 MultiBinding
).最好的方法是從 Binding
繼承并覆蓋它的 ProvideValue
,但它不是虛擬的,所以不可能這樣做,而且我沒有找到任何其他可以覆蓋的方法來達到結果.這是一個只有 2 個綁定屬性的草圖:
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;
}
}
然后用法被簡化為:
<TextBlock Text="{my:LocBinding ActivityCode, Converter={StaticResource myConverter}}" />
您可以根據需要添加更多屬性(如 Mode
等).
You can add more properties (like Mode
and so on) as needed.
這篇關于WPF 運行時區域設置更改,重新評估 ValueConverters UI的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!