Binding.Path binding [duplicate]

2019-07-30 00:10发布

This question already has an answer here:

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.

3条回答
We Are One
2楼-- · 2019-07-30 00:46

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楼-- · 2019-07-30 00:50

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.

查看更多
Animai°情兽
4楼-- · 2019-07-30 01:01

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).

查看更多
登录 后发表回答