Steven C. Britton
Steven C. Britton

Reputation: 482

Binding - dynamically choosing your data for a ListView column

Here's the situation: I have a list of objects, each referenced with an ID number. I'm trying to set a value in a column of a listview based on the ID number of the object:

<ListView     Name="Listview1"
              Itemsource="{Binding ObjectList}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Header1"    DisplayMemberBinding="{Binding subObject.property}"/>
            <GridViewColumn Header="Header2"    DisplayMemberBinding="{Binding PropertyOfAnItemInAList"/>
        </GridView>
    </ListView.View>
</ListView>

ObjectList is a property within the ViewModel, and proves an ObservableCollection to the ListView for processing. Header1 and Header2 are bound to properties within the items found in the ObservableCollection called "ObjectList".

This all works fine for Header1. The problem is Header2 needs to select one item from a list residing inside each element of the ObservableCollection, and display it based on another property of the referenced items in ObjectList.

So how do I do this?

I've managed to get it working by setting up a variable inside the objects in ObjectList called "currentSelection", which then gets set when the binding iterates through ObjectList from ItemSource to produce the ListView:

public ObservableCollection<theObject> ObjectList {

    get {
        foreach (theObject obj in sourceCollection) {

           obj.CurrentID = MyID;
        }

        return new ObservableCollection<theObject>sourceCollection // <-- secondary question:  Is this the best way to send an observableCollection to a View???
    }
}

However, this feels like a bit of a hack rather than a best practice, so is there another, better way to accomplish the same thing?

EDIT:

Here's a snippet of the objects inside ObjectList, as I have it now.

public class ObjectInObjectList {

    /* constructor and various properties snipped to save space */

    private subObject subObj;

    public int CurrentID {get; set;}

    public SubObject {

         get { return subObject; } // etc...
    }

    private SortedList<int,int> valuesIWantInSecondColumn

    Public int PropertyOfAnItemInAList {

         return valuesIWantInSecondColumn[currentID];
    }

Upvotes: 0

Views: 1437

Answers (2)

Lee O.
Lee O.

Reputation: 3312

I would drop the CurrentID property from the object. I don't want to have UI properties in my domain objects. I think the best solution is to use a multibinding for that column and give it the SortedList and your MyID value from the ViewModel, use a converter to return the selected item from the sortedlist. Something like this (assuming the listed combobox as well):

    <ComboBox Grid.Column="0" ItemsSource="{Binding Ids}" SelectedItem="{Binding MyID}" />
    <ListView Name="Listview1" Grid.Column="1" ItemsSource="{Binding ObjectList}">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Header1" DisplayMemberBinding="{Binding subObject.property}"/>
                <GridViewColumn Header="Header2" >
                    <GridViewColumn.DisplayMemberBinding>
                        <MultiBinding Converter="{StaticResource IDictionarySelector}">
                            <Binding Path="PropertyOfAnItemInAList" />
                            <Binding Path="DataContext.MyID" RelativeSource="{RelativeSource AncestorType={x:Type Window}}" />
                        </MultiBinding>
                    </GridViewColumn.DisplayMemberBinding>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>

I tested this code and had to add the ToString() call to the return otherwise it was giving me a binding error that the textblock required a string value (thanks Snoop):

public class IDictionarySelector : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (values != null && values.Length == 2)
        {
            IDictionary idictionary = values[0] as IDictionary;
            int? key = values[1] as int?;

            if ((idictionary != null) && (key != null) && (idictionary.Contains(key)))
            {
                return idictionary[key].ToString();
            }
        }

        return null;
    }

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

Upvotes: 2

Sheridan
Sheridan

Reputation: 69979

You just need to implement the INotifyPropertyChanged Interface on the properties in your ObjectInObjectList class. In particular, you need to tell the interface that the PropertyOfAnItemInAList property has changed when the CurrentID property has changed:

public int CurrentID
{
   get { return currentID; }
   set 
   {
       currentID = value;
       NotifyPropertyChanged("CurrentID");
       NotifyPropertyChanged("PropertyOfAnItemInAList");
   }
}

This notification will update the PropertyOfAnItemInAList property in the UI each time the CurrentID property is changed, so you don't need any special kind of Binding.

Upvotes: 0

Related Questions