0

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; }
}
Community
  • 1
  • 1
Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • Just to clarify your question: do you want to set the `ViewModel`'s `A` property in the `OnPropertyChanged` callback of a dependency property that is bound to that `A` property? That makes no sense. – dymanoid Jan 12 '17 at 14:53
  • @dymanoid, this is why I am here. I am not sure how to organize that using attached property. If e.g. I'd use `bool` and set `local:MyBehavior.View="True"` - then this will cause callback to run, where I can do something like `((ViewModel)element.DataContext).A = something`. Now I want that `A` to be defined in the xaml. Therefore binding. And the question which doesn't make sense ;) – Sinatr Jan 16 '17 at 09:40
  • [Similar question](https://stackoverflow.com/q/11121057/1997232). – Sinatr Oct 18 '19 at 07:35

1 Answers1

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...");
            }
        };
    }
}
mm8
  • 163,881
  • 10
  • 57
  • 88
  • 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). – Sinatr Jan 16 '17 at 09:47
  • Haven't though about reflection, thanks. However binding is more flexible: I can bind to parent control context, by `ElementName`, providing converters, validation, etc.. My **only** problem is to figure out what type `View` should be (btw, in last edit version it's better to use `string`) and how to set source value (what you do with reflection now). Ideas? ;) – Sinatr Jan 16 '17 at 10:21
  • What type View should be depends on the type of the source property. If the source property can be any type, the View property should be an object. – mm8 Jan 16 '17 at 10:32
  • Agree. This is true if you want to call `SetValue()` on that dependency property (which in turn will set source of the binding). But what if you call `GetValue()` to get something (in your last edit it's a name of property, in my failed attempt it's a binding as `BindingBase`) and **then** do something with it to set something. Does this makes it more clear? You see, with reflection you are currently passing the **name** of property. I was thinking to rather pass directly source.. as a binding.. and there I stuck.. I have binding, but don't know what to do with it to set its (binding?) source. – Sinatr Jan 16 '17 at 10:40