使用DependencyProperties的IMarkupExtension

时间:2012-08-09 00:14:19

标签: silverlight silverlight-5.0 markup-extensions

我正在尝试使用IMarkupExtension<T>创建自定义标记扩展,该扩展具有一些用于绑定的DependencyProperties。但是,我正在努力解决在XAML分析时解析标记扩展的问题,以及稍后的绑定。我似乎没有通过绑定得到一些东西:它们总是为空而且从不调用它们的变化回调。

docs提到了有关返回标记扩展实例的内容(在“返回当前标记扩展实例”下),但这似乎会使内容爆炸,因为它是目标的错误类型。 This SL5 MultiBinding似乎返回了一个代理绑定到内部源对象,但我无法让它工作:我的绑定仍然没有设置。

我似乎无法找到任何可靠的信息如何使用DependencyProperties实际实现标记扩展(尽管看起来很多人对SL5感到兴奋......)。任何人都可以提供任何指导或教程吗?

具体来说,我要做的是创建一个标记扩展,可以动态构建一个绑定到列表的路径,如下所示:

{my:ListLookup ListPath='List' Index={Binding Index}}

我希望它基本上输出一个看起来像{Binding List[Index]}的Binding,其中Index是动态的。例如,在列表和索引上执行此操作的目的是使我们直接绑定到对象并获取更改通知。 (如果有更好的方法可以做到这一点......)

1 个答案:

答案 0 :(得分:1)

我已经摆弄了这么多,我找到了解决方案。它基于我在问题中链接的SL5 MultiBinding的实现。

诀窍是MarkupExtension上的绑定将永远不会被评估,因为它没有DataContext或其他东西,但是如果从它获取BindingExpression并将其抛入代理附加属性(附加到目标对象)然后你可以得到Binding来解决。

下面是一个简单的MarkupExtension来演示这一点。所有它正在做的是采用单个Binding并输出其值(适当地遵守变化),但它显示了它如何结合在一起。这可以扩展到解决我正在谈论的字典问题,以及一般的这个问题。

public class SimpleBindingMarkupExtension : DependencyObject, IMarkupExtension<object>, INotifyPropertyChanged
{
    public object Binding
    {
        get { return (object)GetValue(BindingProperty); }
        set { SetValue(BindingProperty, value); }
    }

    public static readonly DependencyProperty BindingProperty =
        DependencyProperty.Register(
            "Binding",
            typeof(object),
            typeof(SimpleBindingMarkupExtension),
            new PropertyMetadata(null));

    public static readonly DependencyProperty ProxyAttachedBindingProperty =
        DependencyProperty.RegisterAttached(
            "ProxyAttachedBinding",
            typeof(object),
            typeof(SimpleBindingMarkupExtension),
            new PropertyMetadata(null, OnProxyAttachedBindingChanged));

    public static readonly DependencyProperty AttachedMarkupExtensionProperty =
        DependencyProperty.RegisterAttached(
            "AttachedMarkupExtension",
            typeof(SimpleBindingMarkupExtension),
            typeof(SimpleBindingMarkupExtension),
            new PropertyMetadata(null));

    private object _bindingSource;
    public object BindingSource
    {
        get { return _bindingSource; }
        set
        {
            _bindingSource = value;
            OnPropertyChanged("BindingSource");
        }
    }

    private static void OnProxyAttachedBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        // Pull the MarkupExtension from the attached property
        var markupExtension = (SimpleBindingMarkupExtension) d.GetValue(AttachedMarkupExtensionProperty);
        markupExtension.ProxyAttachedBindingChanged(e.NewValue);
    }

    private void ProxyAttachedBindingChanged(object value)
    {
        BindingSource = value;
    }

    public object ProvideValue(IServiceProvider serviceProvider)
    {
        IProvideValueTarget target = (IProvideValueTarget) serviceProvider.GetService(typeof (IProvideValueTarget));

        DependencyObject targetObject = target.TargetObject as DependencyObject;
        if (targetObject == null)
            return null;

        // Attach this MarkupExtension to the object so we can find it again from attached property change callbacks
        targetObject.SetValue(AttachedMarkupExtensionProperty, this);

        // Put binding onto proxy attached property, so it actually evaluates
        var localValue = ReadLocalValue(BindingProperty);
        var bindingExpression = localValue as BindingExpression;
        if (bindingExpression == null)
        {
            return localValue;
        }

        Binding originalBinding = bindingExpression.ParentBinding;
        BindingOperations.SetBinding(targetObject, ProxyAttachedBindingProperty, originalBinding);

        // Give the target a proxy Binding that binds to a property on the MarkupExtension
        Binding binding = new Binding
        {
            Path = new PropertyPath("BindingSource"),
            Source = this
        };

        return binding.ProvideValue(serviceProvider);
    }

    #region INotifyPropertyChanged

    private void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

用法:

<TextBlock Text="{local:SimpleBindingMarkupExtension Binding={Binding Text}}"/>

如上所述,此示例将产生与仅说Text="{Binding Text}"相同的结果,但会显示解决方案。