Change Source of Binding for attached property

两盒软妹~` 提交于 2019-12-25 08:08:00

问题


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; }
}

回答1:


Which type to use for attached property?

The same type as you have defined the source property in the view model with, i.e. 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, e.g.:

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...");
            }
        };
    }
}


来源:https://stackoverflow.com/questions/41615291/change-source-of-binding-for-attached-property

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!