简体   繁体   中英

Change Source of Binding for attached property

If I use binding extension in attached property

<TextBlock local:MyBehavior.View="{Binding A}" /> <!-- or B -->

How can set value of A (or B ) property of ViewModel from that attached behavior?

I do not understand:

  1. Which type to use for attached property? Binding ? BindingBase ? BindingExpressionBase ? object ?
  2. Shall I set the value immediately? Shall I wait for some event? Shall I use another dependency property to set it's value, then bind to SomeProperty and let binding do the job once DataContext is set?

My failed attempt is here , for convenience I copied it below:

public class MyBehavior
{
    public static BindingBase GetView(DependencyObject obj) => (BindingBase)obj.GetValue(ViewProperty);
    public static void SetView(DependencyObject obj, BindingBase value) => obj.SetValue(ViewProperty, value);
    public static readonly DependencyProperty ViewProperty =
        DependencyProperty.RegisterAttached("View", typeof(BindingBase), typeof(MyBehavior), new PropertyMetadata(null, (d, e) =>
        {
            var element = d as FrameworkElement;
            if (element == null)
                throw new ArgumentException("Only used with FrameworkElement");
            element.Loaded += (s, a) => GetView(element); // <<
        }));
}

I am not sure what to do at marked line to set value of given by binding property:

public class ViewModel
{
    public object A { get; set; }
    public object B { get; set; }
}

Which type to use for attached property?

The same type as you have defined the source property in the view model with, ie object or whatever type of value that the attached property should store. The type depends on the type of value you intend to store in the attached property.

Shall I set the value immediately

You can specify a default value for a dependency property when you register it. The default value for a reference type such as object is typically null :

public class MyBehavior
{
    public static object GetView(DependencyObject obj) => obj.GetValue(ViewProperty);
    public static void SetView(DependencyObject obj, object value) => obj.SetValue(ViewProperty, value);

    public static readonly DependencyProperty ViewProperty =
        DependencyProperty.RegisterAttached("View", typeof(object), typeof(MyBehavior), 
            new PropertyMetadata(/*default value: */ null, new PropertyChangedCallback(OnPropertyChanged)));

    private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        //...
    }
}

The value of the dependency property will be set automatically when you bind to the view model just like any other dependency property, eg:

MainWindow.xaml.cs:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }

    public object A { get; set; } = "value...";
}

MainWindow.xaml:

<TextBlock local:MyBehavior.View="{Binding A}" />

public class MyBehavior
{
    public static object GetView(DependencyObject obj) => obj.GetValue(ViewProperty);
    public static void SetView(DependencyObject obj, object value) => obj.SetValue(ViewProperty, value);

    public static readonly DependencyProperty ViewProperty =
        DependencyProperty.RegisterAttached("View", typeof(object), typeof(MyBehavior), 
            new PropertyMetadata(/*default value: */ null, new PropertyChangedCallback(OnPropertyChanged)));

    private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        object theValue = GetView(d);
        MessageBox.Show(theValue.ToString());
    }
}

If you want to be able to set the view model property to the value of your attached property you should set the Mode of the binding to OneWayToSource :

<TextBlock x:Name local:MyBehavior.View="{Binding A, Mode=OneWayToSource}" />

But then it is the view that updates the view model:

public MainWindow()
{
    InitializeComponent();
    DataContext = this;

    MyBehavior.SetView(txt, "new value...");
}

The attached dependency property isself is just another dependency property that can be set on any DependencyObject.

Edit:

This solution still require code-behind, my intent to eliminate it with attached behavior. Ideas? Notice element.Loaded (see also my comment to @dymanoid), idea was to set the value from attached behavior and only use binding to pass source (defining which property, A or B, should get that value).

Then you could simply set your attached property to the name of the source property:

<TextBlock local:MyBehavior.View="A" />

...and use reflection to set the value of this source property in your attached behaviour:

public class MyBehavior
{
    public static object GetView(DependencyObject obj) => obj.GetValue(ViewProperty);
    public static void SetView(DependencyObject obj, object value) => obj.SetValue(ViewProperty, value);

    public static readonly DependencyProperty ViewProperty =
        DependencyProperty.RegisterAttached("View", typeof(object), typeof(MyBehavior), 
            new PropertyMetadata(/*default value: */ null, new PropertyChangedCallback(OnPropertyChanged)));

    private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var element = d as FrameworkElement;
        if (element == null)
            throw new ArgumentException("Only used with FrameworkElement");
        element.Loaded += (s, a) => 
        {
            string propertyName = GetView(element).ToString();
            if(element.DataContext != null)
            {
                System.Reflection.PropertyInfo pi = element.DataContext.GetType().GetProperty(propertyName);
                pi.SetValue(element.DataContext, "new value...");
            }
        };
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM