IMarkupExtension with DependencyProperties

402 Views Asked by At

I'm trying to create a custom markup extension using IMarkupExtension<T> that has some DependencyProperties for binding. However, I am struggling to resolve the problem of the markup extension being resolved at XAML parse time, and the bindings only later. I don't seem to ever get something through the bindings: they're always null and never call their change callback.

The docs mention something about returning the instance of the markup extension (under "Returning the Current Markup Extensions Instance"), but that seems to make stuff explode because it's the wrong type for the target. This SL5 MultiBinding seems to return a proxy binding to an internal source object, but I can't manage to get that working: my bindings still don't ever set.

I can't seem to find any solid information how how to actually implement markup extensions with DependencyProperties (even though it seemed like something a lot of people were excited about with SL5...). Can anyone offer any guidance or tutorials?

Specifically, what I'm trying to do is create a markup extension that can dynamically construct a path to do a binding to a list, like so:

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

I'm wanting it to basically output a Binding that would look like {Binding List[Index]}, where Index is dynamic. The purpose of doing this over, say, a MultiBinding on the list and index, is so that we are binding directly to the object and get change notifications. (If there's a better way of doing this...)

1

There are 1 best solutions below

1
On BEST ANSWER

I've fiddled with this a lot more and I've found the solution. It's based on the implementation of the SL5 MultiBinding that I linked to in the question.

The trick is that a Binding on a MarkupExtension will never be evaluated because it doesn't have a DataContext or something, but if you take the BindingExpression from it and throw it into a proxy Attached Property (attached to the target object) then you can get the Binding to resolve.

Below is a simple MarkupExtension that demonstrates this. All it's doing is taking a single Binding and outputting its value (obeying changes appropriately), but it shows how it holds together. This can be extended to solve the dictionary issue I was talking about, along with this problem in general.

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
}

Usage:

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

As mentioned, this example will produce the same result as just saying Text="{Binding Text}", but shows the solution.