Binding.Path

Как я могу добиться чего-то вроде следующего

<CheckBox Content="{Binding Caption}">
    <CheckBox.IsChecked>
        <Binding Path="{Binding PropertyName}"
                 Source="{Binding Source}" />
    </CheckBox.IsChecked>
</CheckBox>

куда

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

Идея состоит в том, чтобы поставить Path а также Source (неизвестно во время разработки) для привязки через свойства.

В настоящее время это исключение броска в <Binding Path= линия

"Связывание" не может быть установлено в свойстве "Путь" типа "Связывание". "Связывание" может быть установлено только для свойства DependencyObject объекта DependencyObject.

3 ответа

Решение

Я пойду с поведением. Нижеприведенное поведение получит Source и Path и обновит привязку соответственно для свойства IsChecked. Вы можете расширить это, чтобы удовлетворить ваши потребности. на данный момент это ограничено IsChecked Property, вы можете написать общий код для поддержки всех свойств.

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

И использование Xaml,

 <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 от модели, и именно там я храню имя свойства.

Примечание: вам понадобится интерактивная сборка.

Имена исходных свойств должны быть известны во время компиляции, чтобы вы могли установить привязку в XAML:

<CheckBox Content="{Binding Caption}">
    <CheckBox.IsChecked>
        <Binding Path="Source.Property" />
    </CheckBox.IsChecked>
</CheckBox>

Как говорится в сообщении об ошибке, вы не можете связать что-либо со свойством Path привязки.

Если вы не знаете имен свойств, с которыми нужно связываться во время разработки, вы можете настроить привязки программно:

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

Нет способа сделать это в чистом XAML. Помните, что XAML - это язык разметки.

Немного запоздалый ответ после просмотра @WPFUser, но он поддерживает любое свойство, и мне лично не нравятся зависимости Blend:

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

Использование:

<CheckBox Content="{Binding Caption}"
          local:DynamicBinding.Property="{Binding PropertyName}"
          local:DynamicBinding.Source="{Binding Source}"
          local:DynamicBinding.Target="IsChecked" />

Target может быть любым свойством зависимости элемента управления. Это дано в виде простой строки, не знаю, как я могу улучшить это, чтобы получить помощь intellisense при вводе.

ToDo: привязка не удаляется, если Target изменено (это будет отражать изменения, сделанные в Source или же Property хотя), нет поддержки нескольких динамических привязок (например, к различным свойствам управления).

Другие вопросы по тегам