问题
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:
- Which type to use for attached property?
Binding
?BindingBase
?BindingExpressionBase
?object
? - 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 onceDataContext
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