IMarkupExtension с DependencyProperties

Я пытаюсь создать собственное расширение разметки, используя IMarkupExtension<T> у которого есть некоторые свойства DependencyProperties для привязки. Однако я изо всех сил пытаюсь решить проблему расширения разметки, решаемого во время разбора XAML, и привязок только позже. Кажется, я никогда не получаю что-то через привязки: они всегда равны нулю и никогда не вызывают обратный вызов.

В документах упоминается что-то о возвращении экземпляра расширения разметки (в разделе "Возврат текущего экземпляра расширений разметки"), но это, кажется, заставляет вещи взрываться, потому что это неправильный тип для цели. Кажется, эта мультисвязка SL5 возвращает прокси-привязку к внутреннему исходному объекту, но мне не удается заставить это работать: мои привязки до сих пор не установлены.

Кажется, я не могу найти какую-либо достоверную информацию о том, как на самом деле реализовать расширения разметки с DependencyProperties (хотя казалось, что многие люди были взволнованы с SL5...). Кто-нибудь может предложить какие-либо рекомендации или учебные пособия?

В частности, я пытаюсь создать расширение разметки, которое может динамически создавать путь для привязки к списку, например так:

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

Я хочу, чтобы он в основном вывел Binding, который будет выглядеть {Binding List[Index]}где Индекс является динамическим. Цель этого, скажем, MultiBinding для списка и индекса, заключается в том, чтобы мы связывались непосредственно с объектом и получали уведомления об изменениях. (Если есть лучший способ сделать это...)

1 ответ

Решение

Я много поиграл с этим и нашел решение. Он основан на реализации SL5 MultiBinding, на которую я ссылался в этом вопросе.

Хитрость заключается в том, что Binding для MarkupExtension никогда не будет оцениваться, потому что у него нет DataContext или чего-то еще, но если вы возьмете из него BindingExpression и бросите его в Присоединенное свойство прокси (присоединенное к целевому объекту), тогда вы можете получить привязку, чтобы решить.

Ниже приведено простое MarkupExtension, которое демонстрирует это. Все, что он делает, это берет одну привязку и выводит ее значение (соблюдая изменения соответствующим образом), но это показывает, как оно сохраняется вместе. Это может быть расширено для решения проблемы со словарем, о которой я говорил, наряду с этой проблемой в целом.

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}", но показывает решение.

Другие вопросы по тегам