【问题标题】:WPF: Why is my markup extension binding not working?WPF:为什么我的标记扩展绑定不起作用?
【发布时间】:2022-11-20 02:40:55
【问题描述】:

“绑定”的工作示例:

我有一个 UserControl,我在主窗口中使用它:

<userControls:NoMarkupControl/>

我的 MainWindow 的 ViewModel 包含此属性:

private string _exampleText = "example";
public string ExampleText
{
   get { return _exampleText; }
   set
   {
      _exampleText = value;
      OnPropertyChanged();
   }
}

在 UserControl 中,我将我的 ViewModel 绑定到这个属性:

<TextBlock Text="{Binding ExampleText}"/>

因此,当我启动应用程序时,会显示“示例”。一切正常。

不使用自定义标记扩展的示例:

现在我有一个 MarkupExtension:

public class ExampleTextExtension : MarkupExtension
{
    private static readonly List<DependencyProperty> StorageProperties = new List<DependencyProperty>();

    private readonly object _parameter;

    private DependencyProperty _dependencyProperty;

    public ExampleTextExtension(object parameter)
    {
        _parameter = parameter;
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
        DependencyObject targetObject;
        if (target?.TargetObject is DependencyObject dependencyObject &&
            target.TargetProperty is DependencyProperty)
        {
            targetObject = dependencyObject;
        }
        else
        {
            return this;
        }

        _dependencyProperty = SetUnusedStorageProperty(targetObject, _parameter);

        return GetLocalizedText((string)targetObject.GetValue(_dependencyProperty));
    }

    private static string GetLocalizedText(string text)
    {
        return text == null ? null : $"markup: {text}";
    }

    private static DependencyProperty SetUnusedStorageProperty(DependencyObject obj, object value)
    {
        var property = StorageProperties.FirstOrDefault(p => obj.ReadLocalValue(p) == DependencyProperty.UnsetValue);

        if (property == null)
        {
            property = DependencyProperty.RegisterAttached("Storage" + StorageProperties.Count, typeof(object), typeof(ExampleTextExtension), new PropertyMetadata());
            StorageProperties.Add(property);
        }

        if (value is MarkupExtension markupExtension)
        {
            var resolvedValue = markupExtension.ProvideValue(new ServiceProvider(obj, property));
            obj.SetValue(property, resolvedValue);
        }
        else
        {
            obj.SetValue(property, value);
        }

        return property;
    }

    private class ServiceProvider : IServiceProvider, IProvideValueTarget
    {
        public object TargetObject { get; }
        public object TargetProperty { get; }

        public ServiceProvider(object targetObject, object targetProperty)
        {
            TargetObject = targetObject;
            TargetProperty = targetProperty;
        }

        public object GetService(Type serviceType)
        {
            return serviceType.IsInstanceOfType(this) ? this : null;
        }
    }
}

我再次有一个 UserControl,我在主窗口中使用它:

&lt;userControls:MarkupControl/&gt;

我的 MainWindow 的 ViewModel 和上面一样。

在 UserControl 中,我像这样绑定到我的 TextBlock Text 属性:

&lt;TextBlock Text="{markupExtensions:ExampleText {Binding ExampleText}}"/&gt;

结果我的 UserControl 什么都不显示。我本来希望显示“标记:示例”

在这种情况下,绑定以某种方式不起作用。

有人知道如何解决这个问题吗?

附加信息:

它像这样使用时有效(依赖属性 MarkupText 在用户控件中创建):

&lt;userControls:MarkupControl MarkupText={markupExtensions:ExampleText {Binding ExampleText}}/&gt;

&lt;TextBlock Text="{Binding Text, ElementName=MarkupControl}"/&gt;

【问题讨论】:

  • 为什么要标记扩展?而不是动态资源或只是视图模型中的属性?
  • 您必须将传入的 Binding 设置为依赖属性才能激活它。绑定引擎实际上完成了将目标属性连接到源属性的所有工作。绑定引擎是依赖属性基础结构的一部分。这就是为什么绑定目标必须是一个依赖属性。您需要创建一个中间依赖属性来解析绑定。处理绑定事件 SourceUpdated 和 TargetUpdated 以捕获更新后的值。然后处理/操纵它并将其发送到您的自定义标记扩展的目标。
  • 要附加 Binding,您的中间属性必须由 DependencyObject 定义。这意味着您需要创建一个专用类来解析绑定。
  • @Andy 我创建这个标记扩展只是为了显示什么不起作用,我真正的标记扩展处理某种语言更改。我也可以在 VM 中执行此操作,但我认为标记扩展可以使它更干净并且(如果工作的话)更易于使用
  • @BionicCode 我不确定我是否理解你。我以为我已经在使用一个依赖属性:property = DependencyProperty.RegisterAttached("Storage" + StorageProperties.Count, typeof(object), typeof(ExampleTextExtension), new PropertyMetadata()); 在这里我将 dp 链接到一个依赖对象:var resolvedValue = markupExtension.ProvideValue(new ServiceProvider(obj, property)); obj.SetValue(property, resolvedValue); 你能发布一个例子或者试着说明你的意思吗?绑定基本上可以正常工作,只是不适用于我的问题中发布的情况

标签: wpf binding markup-extensions


【解决方案1】:

首先,您需要重构您的扩展以简化实现。您在这里不需要静态上下文。摆脱类上下文将使创建的附加属性的跟踪过时。您可以安全地删除相关集合。在您的情况下,将值存储在实例上下文中会更有效。附加属性也是一种方便的解决方案,可以按实例存储值,尤其是在 static 上下文中。

其次,你遇到了时间问题。第一次调用扩展时,Binding 未正确初始化:它不提供 Binding.Source 的最终值。 此外,您当前的实现不支持属性更改。
要解决此问题,您必须在从 Binding.Source(默认为 BindingMode.OneWay)发送值时跟踪 Binding.Target 更新。您可以通过监听 Binding.TargetUpdated 事件(如我之前的评论中所述)或使用附加属性注册属性更改处理程序(推荐)来实现此目的。
要支持双向绑定,您还必须跟踪目标属性(您的 MarkupExtension 分配给的属性)。

修复和改进的版本可能如下所示:

public class ExampleTextExtension : MarkupExtension
{
  private static DependencyProperty ResolvedBindingSourceValueProperty = DependencyProperty.RegisterAttached(
    "ResolvedBindingSourceValue",
    typeof(object),
    typeof(ExampleTextExtension),
    new PropertyMetadata(default(object), OnResolvedBindingSourceValueChanged));  

  // Use attached property to store the target object
  // for reference from a static context without dealing with class level members that are shared between instances.
  private static DependencyProperty TargetPropertyProperty = DependencyProperty.RegisterAttached(
    "TargetProperty",
    typeof(DependencyProperty),
    typeof(ExampleTextExtension),
    new PropertyMetadata(default));

  private Binding Binding { get; }

  // Accept BindingBase to support MultiBinding etc.
  public ExampleTextExtension(Binding binding)
  {
    this.Binding = binding;
  }

  public override object ProvideValue(IServiceProvider serviceProvider)
  {
    var provideValueTargetService = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
    if (provideValueTargetService?.TargetObject is not DependencyObject targetObject
      || provideValueTargetService?.TargetProperty is not DependencyProperty targetProperty)
    {
      return this;
    }

    targetObject.SetValue(ExampleTextExtension.TargetPropertyProperty, targetProperty);
    AttachBinding(targetObject);
    return string.Empty;
  }

  private static string GetLocalizedText(string text) 
    => String.IsNullOrWhiteSpace(text) 
      ? string.Empty 
      : $"markup: {text}";

  // By now, only supports OneWay binding
  private void AttachBinding(DependencyObject targetObject)
  {
    switch (this.Binding.Mode)
    {
      case BindingMode.OneWay:
      case BindingMode.Default:
        HandleOneWayBinding(targetObject); break;
      default: throw new NotSupportedException();
    }
  }

  private void HandleOneWayBinding(DependencyObject targetObject)
  {
    BindingOperations.SetBinding(targetObject, ExampleTextExtension.ResolvedBindingSourceValueProperty, this.Binding);
  }

  private static void OnResolvedBindingSourceValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    var targetProperty = d.GetValue(ExampleTextExtension.TargetPropertyProperty) as DependencyProperty;
    string localizedText = GetLocalizedText(e.NewValue as string);
    d.SetValue(targetProperty, localizedText);
  }
}

【讨论】:

    猜你喜欢
    • 2011-01-16
    • 2011-09-29
    • 1970-01-01
    • 2016-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多