使用非字符串 DataBinding 值的 MarkupExtension
MarkupExtension that uses a non-string DataBinding value
我正在尝试为 WPF 创建一个 MarkupExtension 以用于翻译。我在这里发现了一些类似的问题,包括
MarkupExtension that uses a DataBinding value
How do I resolve the value of a databinding inside a MarkupExtension?
最终,这导致 response by Torvin 看起来很有前途。但是,就像评论中的一个人一样,我有一个问题,即 target.GetValue()
获得的值总是返回 null。
这是部分代码。
最终我有一组静态 classes,其中包含一个静态 KeyDefinition 对象,如下所示
Public class KeyDefinition
{
Public string Key {get; set;}
Public string DefaultValue {get; set;}
}
密钥与 JSON 资源相关联,而默认值是英语翻译,我们可以将其用于 xaml.
的设计时显示
本地化通过静态 class 发生,就像这样 Localize.GetResource(key)
我的目标是XAML这样写
<TextBlock Text="{Localize {Binding KeyDefinitionFromDataContext}}">
其中 KeyDefinitionFromDataContext
是视图模型中的 属性,returns 对 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
,因为那是你 return。您必须知道 Binding
(BindingExpression
)在调用扩展时未解析。 XAML 引擎调用扩展并在 Binding
的情况下期望表达式。通常,MarkupExtension
会 return Binding.ProvideValue(serviceProvider)
的结果,即 BindingExpressionBase
。 XAML 引擎稍后将使用此表达式通过实际附加绑定来生成数据。
换句话说,你 return 过早地得到了结果。
除此之外,您还必须知道 MarkupExtension.ProvideValue
只被调用一次。这意味着您的扩展不处理 属性 更改(以防绑定源更改),并且清除绑定不是所需的绑定处理。它甚至无法处理 OneTime
绑定模式。
在本地化的上下文中,期望源 属性 发生变化非常有意义,至少在用户更改本地化时是这样。
您的代码中有更多错误,例如未设置的 _valueProperty
字段。在不扩展 DependencyObject
的类型上定义 DependencyProperty
的目的是什么?它甚至是私人的!您还应该避免混合使用属性和字段。更好地定义(只读)属性而不是字段。从您的扩展返回 this
(类型 MarkupExtension
的实例)在预期类型不是 object
的情况下将不起作用,例如 string
- return null
代替。
你想要的很容易实现。
首先,您必须将 Binding
附加到代理对象,以便允许绑定引擎激活 BindingExpression
(在示例中,这是 BindingResolver
class)。
其次,您必须配置传入绑定以在目标更新时发出通知。然后监听Binding.TargetUpdated
事件实现OneWay
绑定。要实现 TwoWay
和 OneWayToSource
绑定模式,您还必须启用并观察 Binding.SourceUpdated
事件。
最后,从 source/binding 代理检索更改后的值,将其设置为 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));
}
我正在尝试为 WPF 创建一个 MarkupExtension 以用于翻译。我在这里发现了一些类似的问题,包括
MarkupExtension that uses a DataBinding value
How do I resolve the value of a databinding inside a MarkupExtension?
最终,这导致 response by Torvin 看起来很有前途。但是,就像评论中的一个人一样,我有一个问题,即 target.GetValue()
获得的值总是返回 null。
这是部分代码。
最终我有一组静态 classes,其中包含一个静态 KeyDefinition 对象,如下所示
Public class KeyDefinition
{
Public string Key {get; set;}
Public string DefaultValue {get; set;}
}
密钥与 JSON 资源相关联,而默认值是英语翻译,我们可以将其用于 xaml.
的设计时显示本地化通过静态 class 发生,就像这样 Localize.GetResource(key)
我的目标是XAML这样写
<TextBlock Text="{Localize {Binding KeyDefinitionFromDataContext}}">
其中 KeyDefinitionFromDataContext
是视图模型中的 属性,returns 对 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
,因为那是你 return。您必须知道 Binding
(BindingExpression
)在调用扩展时未解析。 XAML 引擎调用扩展并在 Binding
的情况下期望表达式。通常,MarkupExtension
会 return Binding.ProvideValue(serviceProvider)
的结果,即 BindingExpressionBase
。 XAML 引擎稍后将使用此表达式通过实际附加绑定来生成数据。
换句话说,你 return 过早地得到了结果。
除此之外,您还必须知道 MarkupExtension.ProvideValue
只被调用一次。这意味着您的扩展不处理 属性 更改(以防绑定源更改),并且清除绑定不是所需的绑定处理。它甚至无法处理 OneTime
绑定模式。
在本地化的上下文中,期望源 属性 发生变化非常有意义,至少在用户更改本地化时是这样。
您的代码中有更多错误,例如未设置的 _valueProperty
字段。在不扩展 DependencyObject
的类型上定义 DependencyProperty
的目的是什么?它甚至是私人的!您还应该避免混合使用属性和字段。更好地定义(只读)属性而不是字段。从您的扩展返回 this
(类型 MarkupExtension
的实例)在预期类型不是 object
的情况下将不起作用,例如 string
- return null
代替。
你想要的很容易实现。
首先,您必须将 Binding
附加到代理对象,以便允许绑定引擎激活 BindingExpression
(在示例中,这是 BindingResolver
class)。
其次,您必须配置传入绑定以在目标更新时发出通知。然后监听Binding.TargetUpdated
事件实现OneWay
绑定。要实现 TwoWay
和 OneWayToSource
绑定模式,您还必须启用并观察 Binding.SourceUpdated
事件。
最后,从 source/binding 代理检索更改后的值,将其设置为 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));
}