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