Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind a ListView to multiple collections stored in a single ViewModel in WPF?

I have 2 collections I want to bind to a separate GridViewColumn in a ListView:

public class EffectView : INotifyPropertyChanged
{

    ObservableCollection<Effect> effects;
    public ObservableCollection<Effect> Effects
    {
        get { return this.effects; }
        set
        {
            this.effects = value;
            this.RaisePropertyChanged ( "Effects" );
        }
    }

    ObservableCollection<EffectDescription> descriptions;
    public ObservableCollection<EffectDescription> Descriptions
    {
        get { return this.descriptions; }
        set
        {
            this.descriptions = value;
            this.RaisePropertyChanged ( "Descriptions" );
        }
    }
}

I can do this:

<ListView ItemsSource="{Binding EffectView.Effects}">
    <ListView.View>
        <GridView>
            <GridViewColumn Width="Auto"
                            DisplayMemberBinding="{Binding Name}"
                            Header="Name" />

            <GridViewColumn Width="Auto"
                            DisplayMemberBinding="{Binding Opacity}"
                            Header="Opacity" />

            <GridViewColumn Width="Auto"
                            DisplayMemberBinding="{Binding ?}"
                            Header="Description" />
        </GridView>
    </ListView.View>
</ListView>

But then everything is scoped to EffectView.Effects, but I want the default scope to be EffectView so I can easily assign multiple collections to the ListView.

Something like:

<ListView ItemsSource="{Binding EffectView}">
    <ListView.View>
        <GridView>
            <GridViewColumn Width="Auto"
                            DisplayMemberBinding="{Binding Effects Path=Name}"
                            Header="Name" />

            <GridViewColumn Width="Auto"
                            DisplayMemberBinding="{Binding Effects Path=Opacity}"
                            Header="Opacity" />

            <GridViewColumn Width="Auto"
                            DisplayMemberBinding="{Binding Descriptions Path=Usage}"
                            Header="Description" />
        </GridView>
    </ListView.View>
</ListView>

Any way to accomplish this?

like image 894
Joan Venge Avatar asked Mar 23 '11 23:03

Joan Venge


2 Answers

The ItemsSource of a ListView is the collection whose items will appear in the list. So think for a moment about what you're asking the ListView to do:

  • As a source collection, use something that isn't a collection, but contains them
  • For each row in the list, display an item from one collection, and an item from the other collection

Pay attention to that second point: every row must display some item from the Events collection and some item from the Descriptions collection.

Which item shall it pick from each? What's the relationship between the items in the two collections?

It appears that what you really need is a collection of objects that contains both an Event and a Description. You can then bind to that collection to display elements of both entities. Roughly, something like this:

public class EffectView : INotifyPropertyChanged
{

    ObservableCollection<EffectsAndDescriptions> effects;
    public ObservableCollection<EffectAndDescriptions> Effects
    {
        get { return this.effects; }
        set
        {
            this.effects = value;
            this.RaisePropertyChanged ( "EffectsAndDescriptions" );
        }
    }

}

internal class EffectsAndDescriptions
{
    public Effect Effect { get; set; }
    public Description Description { get; set; }
}

Now you can bind to the EffectsAndDescriptions collection (note this assumes the DataContext of the ListView's parent is EffectView)

<ListView ItemsSource="{Binding EffectsAndDescriptions}">
    <ListView.View>
        <GridView>
            <GridViewColumn Width="Auto"
                            DisplayMemberBinding="{Binding Effect.Name}"
                            Header="Name" />

            <GridViewColumn Width="Auto"
                            DisplayMemberBinding="{Binding Effect.Opacity}"
                            Header="Opacity" />

            <GridViewColumn Width="Auto"
                            DisplayMemberBinding="{Binding Description.Usage}"
                            Header="Description" />
        </GridView>
    </ListView.View>
</ListView>
like image 68
Dan J Avatar answered Sep 22 '22 19:09

Dan J


That's not really possible. ItemsSource expects to be passed something that, at a minimum, implements IEnumerable. Even if you implemented IEnumerable on your EffectView, you would need to wrap Effect and EffectDescription into a single object to return.

You could bind your ListView to your Effects collection, then use a custom IValueConverter to take the Effect and return a description. But I'm not sure this would fit your situation.

Your best bet would be to include a wrapper object (i.e. EffectWithDescription) that includes both your Effect and your EffectDescription. Then expose a collection of EffectWithDescription objects. You'd need to ensure that this new collection is keep in sync with the other collections though.

like image 32
CodeNaked Avatar answered Sep 25 '22 19:09

CodeNaked