问题
How can I achieve something like following
<CheckBox Content="{Binding Caption}">
<CheckBox.IsChecked>
<Binding Path="{Binding PropertyName}"
Source="{Binding Source}" />
</CheckBox.IsChecked>
</CheckBox>
Where
public class ViewModel
{
public string Caption { get; } = "Test";
public string PropertyName { get; } = nameof(Test.Property);
public object Source { get; } = new Test();
}
public class Test
{
public bool Property { get; set; } = false;
}
Idea is to supply Path
and Source
(unknown at design time) for the binding via properties.
Currently this throw exception at <Binding Path=
line
A 'Binding' cannot be set on the 'Path' property of type 'Binding'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
回答1:
I'll go with behaviors. Below behavior will get Source and Path and update the binding accordingly for IsChecked Property. You can extend this to meet your need. for now this is limited to IsChecked Property, you can write generic code to support all properties.
public class CheckBoxCustomBindingBehavior : Behavior<CheckBox>
{
protected override void OnAttached()
{
base.OnAttached();
}
public object Source
{
get
{
return (object)GetValue(SourceProperty);
}
set
{
SetValue(SourceProperty, value);
}
}
// Using a DependencyProperty as the backing store for Source. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(object), typeof(CheckBoxCustomBindingBehavior), new PropertyMetadata(null, OnSourceChanged));
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as CheckBoxCustomBindingBehavior).ModifyBinding();
}
public string Path
{
get
{
return (string)GetValue(PathProperty);
}
set
{
SetValue(PathProperty, value);
}
}
// Using a DependencyProperty as the backing store for Path. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PathProperty =
DependencyProperty.Register("Path", typeof(string), typeof(CheckBoxCustomBindingBehavior), new PropertyMetadata(string.Empty, OnPathChanged));
private static void OnPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as CheckBoxCustomBindingBehavior).ModifyBinding();
}
private void ModifyBinding()
{
var source = Source ?? AssociatedObject.DataContext;
if (source != null && !string.IsNullOrEmpty(Path))
{
Binding b = new Binding(Path);
b.Source = source;
AssociatedObject.SetBinding(CheckBox.IsCheckedProperty, b);
}
}
}
And Xaml usage,
<CheckBox xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<i:Interaction.Behaviors>
<local:CheckBoxCustomBindingBehavior Path="{Binding SelectedPath}" Source="{Binding}" />
</i:Interaction.Behaviors>
</CheckBox>
SelectedPath is from model,and that's where i store the Property Name.
Note: you will need Interactivity assembly.
回答2:
The names of the source properties must be known at compile-time for you to be able set up the binding in XAML:
<CheckBox Content="{Binding Caption}">
<CheckBox.IsChecked>
<Binding Path="Source.Property" />
</CheckBox.IsChecked>
</CheckBox>
As the error message tells you, you cannot bind something to the Path property of a Binding.
If you don't know the names of the properties to bind to at design time, you could set up the bindings programmatically:
<CheckBox x:Name="ck" Content="{Binding Caption}" />
ViewModel vm = new ViewModel();
ck.DataContext = vm;
ck.SetBinding(CheckBox.IsCheckedProperty, new Binding(vm.PropertyName) { Source = vm.Source });
There is no way to do this in pure XAML though. Remember that XAML is a markup language.
回答3:
A bit late answer after seeing @WPFUser one, but it supports any property and I personally do not like Blend dependencies:
public class DynamicBinding
{
public static object GetSource(DependencyObject obj) => (object)obj.GetValue(SourceProperty);
public static void SetSource(DependencyObject obj, object value) => obj.SetValue(SourceProperty, value);
public static readonly DependencyProperty SourceProperty =
DependencyProperty.RegisterAttached("Source", typeof(object), typeof(DynamicBinding), new PropertyMetadata(null, (d, e) => SetBinding(d)));
public static string GetProperty(DependencyObject obj) => (string)obj.GetValue(PropertyProperty);
public static void SetProperty(DependencyObject obj, string value) => obj.SetValue(PropertyProperty, value);
public static readonly DependencyProperty PropertyProperty =
DependencyProperty.RegisterAttached("Property", typeof(string), typeof(DynamicBinding), new PropertyMetadata(null, (d, e) => SetBinding(d)));
public static string GetTarget(DependencyObject obj) => (string)obj.GetValue(TargetProperty);
public static void SetTarget(DependencyObject obj, string value) => obj.SetValue(TargetProperty, value);
public static readonly DependencyProperty TargetProperty =
DependencyProperty.RegisterAttached("Target", typeof(string), typeof(DynamicBinding), new PropertyMetadata(null, (d, e) => SetBinding(d)));
static void SetBinding(DependencyObject obj)
{
var source = GetSource(obj);
var property = GetProperty(obj);
var target = GetTarget(obj);
// only if all required attached properties values are set
if (source == null || property == null || target == null)
return;
BindingOperations.SetBinding(obj, DependencyPropertyDescriptor.FromName(target, obj.GetType(), obj.GetType()).DependencyProperty,
new Binding(property) { Source = source });
}
}
The usage is:
<CheckBox Content="{Binding Caption}"
local:DynamicBinding.Property="{Binding PropertyName}"
local:DynamicBinding.Source="{Binding Source}"
local:DynamicBinding.Target="IsChecked" />
Target
can be any dependency property of the control. It's given as a plain string, not sure how I can improve this to get intellisense assistance when entering it.
ToDo: binding is not removed if Target
is changed (it will reflect changes made to Source
or Property
though), no support for multiple dynamic bindings (e.g. to different properties of control).
来源:https://stackoverflow.com/questions/41482038/binding-path-binding