views:

14

answers:

1

Hi.
I didn't find a solution for this but I think it should be doable.

I have a number of items in a collection and want to select some of them. Each item has a CanInclude property containing the elements that can be selected if itself is already selected.

  • Item1 CanInclude: Item4, Item5
  • Item2 CanInclude: Item3, Item4
  • Item3 CanInclude: Item2
  • Item4 CanInclude: Item1
  • Item5 CanInclude: Item2, Item3

A starting element is selected somewhere else.

So if start item is Item1, I want to have a comboBox with Item4 and Item5 in it. If i select Item5 in this comboBox and click on a '+' button I want to get a new Box underneath with Item2, Item3(from last checkbox) and Item4(from start item) and so on till there is no other item that can be selected or the user clicks 'OK'.

I have thought of a simple collection in the viewmodel, where [0] holds the start element, [1] the selected element of 1. comboBox and so on. But i don't know how i should dynamically add the comboBoxes or let a comboBox create the [n] element in the collection of selected items. Also I can't think of a way to include all items of the CanInclude properties of the already selected items in the new checkbox.

I would be very thankfull if anyone had an idea.

EDIT: Just for explenation i want somehting like this (Pseudo code included since you can not do {Binding} + {Binding}, but i think you get the idea):

<ComboBox ItemsSource="{Binding Path=SelectableItems}" SelectedItem="{Binding Path=SelectedItem1}" />
<ComboBox ItemsSource="{Binding Path=SelectedItem1.CanInclude}" SelectedItem="{Binding Path=SelectedItem2}"/>
<ComboBox ItemsSource="{Binding Path=SelectedItem1.CanInclude} + {Binding Path=SelectedItem2.CanInclude} - {Binding Path=SelectedItem1} - {Binding Path=SelectedItem2}" SelectedItem="{Binding Path=SelectedItem3}"/>

But I want it to work for a non fixed number of entries.

+1  A: 

You could template a ListBox so that each item is a combobox. That way you can bind the datasource of the ListBox to an ObservableCollection of ViewModels that represent the items. Something like:

public class TopLevelViewModel
{
    private List<ItemViewModel> _allItems;

    public ObservableCollection<ItemViewModel> CurrentlySelectedItems { get; set; }

    public TopLevelViewModel()
    {
         DefineAllItems()
         SelectFirstItem()
    }

    private void DefineAllItems()
    {
        ItemViewModel item1 = new ItemViewModel { Name = "Item1" }
        item1.SelectedItemChanged += HandleItemViewModelSelectedItemChanged;

        ItemViewModel item2 = new ItemViewModel { Name = "Item2" }
        item2.SelectedItemChanged += HandleItemViewModelSelectedItemChanged;

        ItemViewModel item3 = new ItemViewModel { Name = "Item3" }
        item3.SelectedItemChanged += HandleItemViewModelSelectedItemChanged;

        item1.CanInclude = new ObservableCollection<ItemViewModel>
        {
              item2, item3
        }

        item2.CanInclude = new ObservableCollection<ItemViewModel>
        {
              item3
        }

        _allItems = new List<ItemViewModel>
        {
              item1, item2, item3
        }            
    }

    private void SelectFirstItem()
    {
         //Add item1 as the first combobox
         CurrentlySelectedItems.Add(_allItems[0]);
    }

    private void HandleItemViewModelSelectedItemChanged(object sender, EventArgs e)
    {
        ItemViewModel parent = (ItemViewModel)sender;

        //Find the view model whose item has changed in the CurrentlySelectedItems
        int indexOfParent = CurrentlySelectedItems.IndexOf(parent);

        //Remove all itemviewmodels below that item
        CurrentlySelectedItems.RemoveRange(
                   indexOfParent+1,
                   CurrentlySelectedItems.Count-(indexofParent+1))

        //Add the selected item into the list
        CurrentlySelectedItems.Add(parent.SelectedItem);            
    }
}

public class ItemViewModel
{
    public string Name { get; set; }
    public ObservableCollection<ItemViewModel> CanInclude { get; set; }
    public ItemViewModel SelectedItem { get; set; }

    public event EventHandler SelectedItemChanged;
}

Then bind the ListBox to the CurrentlySelectedItems, and the ComboBox inside the template to the CanInclude:

<ListBox ItemsSource="{Binding CurrentlySelectedItems}">
   <ListBox.ItemTemplate>
       <DataTemplate>
          <ComboBox ItemsSource="{Binding CanInclude}"
                    DisplayMemberPath="{Binding Title}"
                    SelectedItem="{Binding SelectedItem}"/>
       </DataTemplate>
   </ListBox.ItemTemplate>
</ListBox>

Edited to add a solution that better fits the problem:

Xaml:

<ListBox ItemsSource="{Binding PreviouslySelectedItems}"/>
<ComboBox ItemsSource="{Binding CanInclude}"
          DisplayMemberPath="{Binding Title}"
          SelectedItem="{Binding SelectedItem}"/>
<Button Content="Add"
        Command="{Binding AddCommand}"/>

ViewModel:

public class TopLevelViewModel
{
      public ObservableCollection<ItemViewModel> PreviouslySelectedItems { get; private set; }
      public ObservableCollection<ItemViewModel> CanInclude { get; private set; }
      public ItemViewModel SelectedItem { get; set; }

      //Set up all the items as above, and pre-populate the first item
      //and the initial CanInclude options.

      //When the Add button is clicked
      public void ExecuteAdd()
      {
           //Add the currently selected item to the Listbox
           PreviouslySelectedItems.Add(SelectedItem)

           //Rebuild the CanInclude list
           CanInclude.Clear();

           var newCanInclude =
               PreviouslySelectedItems.SelectMany(x => x.CanInclude)
                                      .Where(x => !PreviouslySelectedItems.Contains(x))

           CanInclude.AddRange(newCanInclude);
      }
}
Martin Harris
Thanks, but if i get it correctly, this would just generate a list of comboboxes for each item in the ItemList with all items in the CanInclude selectable. But i want to have only one combobox at startup, add comboboxes per button, and want to can only select the items which are in the CanInclude of the comboboxes above itself. I thank for the help, but its far from what i need and does not cover the dynamic add of comboboxes, which is my biggest problem.
Marks
You just add the item that you want to start with as the only item in the ItemsList to start with. Hang on: I'll update the code to make it more explicit.
Martin Harris
Adding first item to ItemsList gives first comboBox with the CanInclude of itself => OK. Clicking '+' button will add selected item of first comboBox to ItemsList with CanInclude of itself. Maybe includes already selected one => Not OK. Third comboBox will have CanInclude only from selected in 2. comboBox, not the ones in 1. and also may include the item already selected in 1 or 2 => Not OK.
Marks
Oh, I see - sorry, I misread the question. If you only have one active combo at a time then this is not too bad. Have a ListBox above the combo that shows all the previously selected items as text and move the selected item up there when you hit the plus button. Then just populate the collection the combobox is bound to with all the CanIncludes from those items minus those items themselves. If you are allowing the user to go back and change earlier selections (which may invalidate the selections below) then the logic is more complicated and you'll need to decide how to handle that.
Martin Harris
I wanted to have multiple staged comboBoxes becouse i think this is the best for the user. I would appriciate if you have any tipps on how to do this. The main problem for me is how to get or store the items, which can be selected in the first, second ... combobox.
Marks