使用非字符串DataBinding值的MarkupExtension



我正在尝试为WPF创建一个用于翻译的MarkupExtension。我在这里发现了一些类似的问题,包括

使用DataBinding值的MarkupExtension

如何解析MarkupExtension中数据绑定的值?

最终,这导致了Torvin的反应,看起来非常有希望。然而,就像评论中的人一样,我有一个问题,target.GetValue()获得的值总是返回null。

下面是一些代码

最终,我有一组静态类,其中包含一个静态KeyDefinition对象,如下所示

Public class KeyDefinition
{
Public string Key {get; set;}
Public string DefaultValue {get; set;}
}

键连接回JSON资源,而DefaultValue是一个英文翻译,我们可以使用它在设计时显示xaml.

通过静态类进行定位,如Localize.GetResource(key)

我的目标是像这样编写XAML

<TextBlock Text="{Localize {Binding KeyDefinitionFromDataContext}}">

其中KeyDefinitionFromDataContext是视图模型中的一个属性,它返回对KeyDefinition对象的引用。

根据Torvin的回应,我创建了一个MarkupExtension,像这样

public class LocalizeExtension : MarkupExtension
{
private readonly BindingBase _binding;
private static readonly DependencyProperty _valueProperty = DependencyProperty.RegisterAttached("Value", typeof(KeyDefinition), typeof(LocalizeExtension));

[ConstructorArgument("keyDefinition")
public KeyDefinition KeyDefinition {get; set;}
public LocalizeExtension(Binding binding)
{
_binding = binding;
}
public LocalizeExtension(KeyDefinition keyDefinition)
{
KeyDefinition = keyDefinition;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var pvt = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
var target = pvt.TargetObject as DependencyObject;
var property = pvt.TargetProperty as DependencyProperty;
//If inside a template, WPF will call again when its applied
if (target == null)
return this;
BindingOperations.SetBinding(target, property, _binding);
KeyDefinition = (KeyDefinition)target.GetValue(_valueProperty);
BindingOperations.ClearBinding(target, property);
return Localize.GetResource(KeyDefinition.Key);
}
}

现在请原谅我,因为我平时不做WPF的工作,但是这个任务落到了我身上。每当我运行这段代码,返回的值总是Null。我试过直接使用字符串而不是'KeyDefinition'对象,但遇到了同样的问题。

我想这里让我困惑的是如何在目标上设置DependencyProperty,因为它是私有的。

任何帮助都是感激的。谢谢!

它不是这样工作的。MarkupExtension的结果总是null,因为这是你返回的。您必须知道,在调用扩展时没有解析Binding(BindingExpression)。XAML引擎调用扩展,并且在Binding的情况下需要一个表达式。通常,MarkupExtension将返回Binding.ProvideValue(serviceProvider)的结果,这是一个BindingExpressionBase。XAML引擎稍后将使用该表达式通过实际附加绑定来生成数据。

也就是说,您提前返回了结果。

除此之外,您还必须知道MarkupExtension.ProvideValue只被调用一次。这意味着您的扩展不处理属性更改(如果绑定源更改),并且清除绑定不是期望的绑定处理。它实际上甚至无法处理OneTime绑定模式。
在本地化的上下文中,期望源属性发生变化是很有意义的,至少在用户更改本地化时是这样。

你的代码中有更多的错误,像一个未设置的_valueProperty字段。在不扩展DependencyObject的类型上定义DependencyProperty的目的是什么?它甚至是私人的!您还应该避免混合属性和字段。最好定义(只读)属性而不是字段。从扩展返回this(MarkupExtension类型的实例)将无法在预期类型不是object的情况下工作,例如,string-返回null

你想要的很容易实现。
首先,您必须将Binding附加到代理对象,以便允许绑定引擎激活BindingExpression(在本例中,这是BindingResolver类)。
其次,您必须配置传入绑定,以便在更新目标时引发通知。然后监听Binding.TargetUpdated事件,实现OneWay绑定。为了实现TwoWayOneWayToSource的绑定模式,还必须启用并观察Binding.SourceUpdated事件。
最后,从源/绑定代理中检索更改的值,将其设置为MarkupExtension的目标。

由于数据绑定通常涉及将DataContext作为源,即需要可视树才能解析,因此绑定代理是一个简单的附加属性。这样做的好处是我们可以使用目标元素的原始DataContext,而不必担心如何将代理注入到可视化树中。

LocalizeExtension.cs

public class LocalizeExtension : MarkupExtension
{
private Binding Binding { get; };
private DependencyObject LocalizationTarget { get; set; }
private DependencyProperty LocalizationTargetProperty { get; set; }
private object LocalizationSource { get; set; }
private string LocalizationPropertyName { get; set; }
private bool IsInitialized { get; set; }

public LocalizeExtension(Binding binding)
{
this.Binding = binding;
this.Binding.NotifyOnTargetUpdated = true;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var serviceProvider = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
this.LocalizationTarget = serviceProvider.TargetObject as DependencyObject;
// If inside a template, WPF will call again when its applied
if (this.LocalizationTarget == null)
{
return null;
}
this.LocalizationTargetProperty = serviceProvider.TargetProperty as DependencyProperty;
BindingOperations.SetBinding(this.LocalizationTarget, BindingResolver.ResolvedBindingValueProperty, this.Binding);
Binding.AddTargetUpdatedHandler(this.LocalizationTarget, OnBindingSourceUpdated);
return null;
}
private void OnBindingSourceUpdated(object sender, EventArgs e)
{
if (!this.IsInitialized)
{
InitializeLocalizationSourceInfo();
}
LocalizeBindingSource();
}
private void InitializeLocalizationSourceInfo()
{
BindingExpression bindingExpression = BindingOperations.GetBindingExpression(this.LocalizationTarget, BindingResolver.ResolvedBindingValueProperty);
this.LocalizationSource = bindingExpression.ResolvedSource;
this.LocalizationPropertyName = bindingExpression.ResolvedSourcePropertyName;
this.IsInitialized = true;
}
private void LocalizeBindingSource()
{
object unlocalizedValue = BindingResolver.GetResolvedBindingValue(this.LocalizationTarget);
object localizedValue = LocalizeValue(unlocalizedValue);
this.LocalizationTarget.SetValue(this.LocalizationTargetProperty, localizedValue);
}
private object LocalizeValue(object value)
{
return value is KeyDefinition keyDefinition 
? Localize.GetResource(keyDefinition.Key) 
: string.Empty;
}
}

BindingResolver.cs

class BindingResolver : DependencyObject
{
public static object GetResolvedBindingValue(DependencyObject obj) => (object)obj.GetValue(ResolvedBindingValueProperty);
public static void SetResolvedBindingValue(DependencyObject obj, object value) => obj.SetValue(ResolvedBindingValueProperty, value);
public static readonly DependencyProperty ResolvedBindingValueProperty =
DependencyProperty.RegisterAttached(
"ResolvedBindingValue", 
typeof(object), 
typeof(BindingResolver), 
new PropertyMetadata(default));
}

最新更新