Kevin Kuebler
Kevin Kuebler

Reputation: 384

Binding a list of elements to a collection-based dependency property in WPF

I'm implementing a custom Blend behavior in a WPF application (.Net 4.5). I've added a couple of dependency properties of type FrameworkElement to the behavior class to allow the user of the behavior to bind the elements of the view that they want to control. (This behavior invokes some animation on multiple elements, so I can't just use the AssociatedObject). This works fine and basically looks like this:

public class MyBehavior : Behavior<FrameworkElement>
{
    public static readonly DependencyProperty Element1Property = DependencyProperty.Register("Element1", typeof (FrameworkElement), typeof (MyBehavior), new UIPropertyMetadata());

    public FrameworkElement Element1
    {
        get { return (FrameworkElement) GetValue(Element1Property); }
        set { SetValue(Element1Property, value); }
    }

    public static readonly DependencyProperty Element2Property = DependencyProperty.Register("Element2", typeof(FrameworkElement), typeof(MyBehavior), new UIPropertyMetadata());

    public FrameworkElement Element2
    {
        get { return (FrameworkElement) GetValue(Element2Property); }
        set { SetValue(Element2Property, value); }
    }
}

Standard dependency property stuff. And I can use that in my view like this:

<Grid x:Name="Container">
    <i:Interaction:Behaviors>
        <local:MyBehavior
            Element1="{Binding ElementName=FirstElement}"
            Element2="{Binding ElementName=SecondElement}"
        />
    </i:Interaction:Behaviors>
</Grid>

This works great and I can work with the elements in the behavior. But now I have a requirement to bind a list of elements like this. So I don't know ahead of time that there are going to be exactly 2 elements, there could be N elements that I need to work with. So I've added another property to the MyBehavior class like this:

public static readonly DependencyProperty ElementsProperty = DependencyProperty.Register("Elements", typeof(List<FrameworkElement>), typeof(MyBehavior), new UIPropertyMetadata(new List<FrameworkElement>()));

public List<FrameworkElement> Elements
{
    get { return (List<FrameworkElement>) GetValue(ElementsProperty); }
    set { SetValue(ElementsProperty, value); }
}

(And I've followed the advice here to initialize the list in the behavior's constructor.) But I can't figure out how to bind the list of elements to this property from the view's XAML. Basically, I want to do something along these lines:

<Grid x:Name="Container">
    <i:Interaction:Behaviors>
        <local:MyBehavior>
            <local:MyBehavior.Elements>
                <Binding ElementName="FirstElement" />
                <Binding ElementName="SecondElement" />
                <Binding ElementName="ThirdElement" />
            </local:MyBehavior.Elements>
        </local:MyBehavior>
    </i:Interaction:Behaviors>
</Grid>

But of course this doesn't actually work. I've tried MultiBinding here, but that doesn't work either. Any idea what the XAML syntax would be for doing this, or is it even possible? If it's not possible, any ideas for other approaches to achieve this effect? Thanks!

Upvotes: 1

Views: 1358

Answers (1)

Kevin Kuebler
Kevin Kuebler

Reputation: 384

I ended up solving this myself. It turns out, I could use a MultiBinding for this. The converter looks like this:

public class MultiFrameworkElementConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        return values != null ?
            values.Cast<FrameworkElement>().ToList() :
            new List<FrameworkElement>();
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

I should be a little more thorough in that converter and ensure that all of the objects in the values[] array are of type FrameworkElement, but this gets the idea across. Then in the XAML I can bind to the property on my behavior like this:

<local:MyBehavior.Elements>
    <MultiBinding Converter="{StaticResource MultiFrameworkElementConverter}" Mode="OneTime">
        <Binding ElementName="FirstElement" />
        <Binding ElementName="SecondElement" />
        <Binding ElementName="ThirdElement" />
    </MultiBinding>
</local:MyBehavior.Elements>

I'm using the "OneTime" mode on the binding simply because these are UI elements in the view that I'm binding to the behavior. They will never change during the lifetime of the view and behavior. So no need to ever update the binding.

Overall, I'm satisfied with this. I can now allow the behavior to affect an arbitrary list of UI elements regardless of which view I use it on. I hope this description is able to help someone else trying to do something similar.

Upvotes: 1

Related Questions