【问题标题】:MarkupExtension that uses a non-string DataBinding value使用非字符串 DataBinding 值的 MarkupExtension
【发布时间】:2021-12-09 13:43:03
【问题描述】:

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

MarkupExtension that uses a DataBinding value

How do I resolve the value of a databinding inside a MarkupExtension?

最终,这导致了看起来很有希望的response by Torvin。但是,作为cmets中的一个人,我有一个问题,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 是如何设置的,因为它是私有的。

感谢任何帮助。谢谢!

【问题讨论】:

  • 您的 LocalizeExtension 类甚至不会编译,至少当 KeyDefinition 是静态类时不会。静态类不能用作属性类型或构造函数参数。
  • 您还必须在 SetBinding、GetValue 和 ClearBinding 调用中使用 _valueProperty 字段,而不是本地的 property 变量(这样就没有用了)。但即使进行了这种修改,这种方法也行不通。它看起来像是偶然发生的事情,您不应该尝试这样做。使用另一种方法并附加一个绑定转换器。
  • 最终目标是本地化吗?我通常通过使用每种语言的资源字典来做到这一点,其中包含所有字符串。以所需的任何语言进行合并,并将动态资源与视图中的键一起使用。在法语中合并,它的键与视图中使用的原始英语键匹配,您会看到法语。
  • 对不起。我写这个问题的时候有一个错字。 KeyDefinition 类不应该是静态的。那已经更新了。我同意你的看法,我认为克莱门斯。我真的不明白这将如何运作。我将尝试另一种方法。
  • 理想情况下,我只需回答其他威胁中的问题并要求澄清,但我没有足够的代表这样做。

标签: c# wpf xaml localization


【解决方案1】:

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

换句话说,你过早地返回了结果。

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

您的代码中有更多错误,例如未设置的_valueProperty 字段。在不扩展DependencyObject 的类型上定义DependencyProperty 的目的是什么?它甚至是私人的!您还应该避免混合属性和字段。更好地定义(只读)属性而不是字段。如果预期类型不是object,例如string,则从您的扩展返回thisMarkupExtension 类型的实例)将不起作用 - 改为返回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));
}

【讨论】:

    猜你喜欢
    • 2013-01-22
    • 2010-09-27
    • 2011-01-28
    • 2011-04-12
    • 1970-01-01
    • 2018-07-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多