views:

147

answers:

2

In my viewmodel, I have a list (ObservableCollection) containing items. In the view, this list is displayed in an ItemsControl. In each row, there is a "Delete" button. I want the command behind the button to remove the item from the list.

<ItemsControl ItemsSource="{Binding myList}">
    <ItemsControl.ItemTemplate>
        ...
            <Button Command="{StaticResource myDeleteCommand}" CommandParameter="???">
                Remove item
            </Button>
        ...
    </ItemsControl.ItemTemplate>
</ItemsControl>

What do I pass as the command parameter?

  • The item itself (Binding .)? Then I don't have a reference to the list in the command, so I'd need to change my model such that each list item contains a back-reference to the list.
  • The list? Then I don't have a reference to the item.
  • Both? Then I need to write a MultiConverter that translates the list plus the item into some custom object. Seems like a lot of overhead for such a simple task.

Any ideas? This seems like a fairly common scenario to me, so I guess there must be some well-established best-practice solution...

+2  A: 

I've implemented such commands in that way, that I pass the Item as Parameter. The command self knows on which list it should operate. Either trough a delegate that calls a Delete Method in my ViewModel or the command receives the list of items in it's constructor.

i.e. a Command with delegates

public sealed class SimpleParameterCommandModel<T> : CommandModel
{
    private readonly Action<T> execute;
    private readonly Func<T, bool> canExecute;

    public SimpleParameterCommandModel(string label, string tooltip, Action<T> execute, Func<T, bool> canExecute)
        : base(appCtx, dataCtx, label, tooltip)
    {
        if (execute == null) throw new ArgumentNullException("execute");
        this.execute = execute;
        this.canExecute = canExecute;
    }
    ...
}

usage:

private ICommand _DeleteCommand = null;
public ICommand DeleteCommand
{
    get
    {
        if (_DeleteCommand == null)
        {
            _DeleteCommand = new SimpleParameterCommandModel<IEnumerable<DataObjectModel>>                      ("Delete", "Delete selection from data store", 
                (items) => items.ToList().ForEach(i => DeleteItem(i)),
                (items) => items != null && items.Count() > 0 && AllowDelete);
        }
        return _DeleteCommand;
    }
}
public void DeleteItem(DataObjectModel item)
{
        if (item == null) { throw new ArgumentNullException("item"); }

    myCollection.Remove(item.Object);
}

EDIT: Forgot XAML

<Button Command="{Binding DeleteCommand, ElementName=...}" CommandParameter="{Binding}">
        Remove item
</Button>
Arthur
Danke! :-) Yes, creating a separate Command for each instance of the list is indeed the solution.
Heinzi
+1  A: 

First, I would handle the Command in the ViewModel. I assume that the list that is being used for binding is in the ViewModel, so any code that does "work" on that list should also be done in the ViewModel.

class MyViewModel
{ 
    // ... Clipping rest of ViewModel class ...

    private ObservableCollection<MyObject> mMyList = new ObservableCollection<MyObject>(); 
    private ICommand mMyDeleteCommand;

    public MyViewModel()
    {
        InitializeMyListSomehow();
        mMyDeleteCommand = new MyCommandClass(
            (item) => DeleteItem(item),
            () => mDeleteCanExecute
        );
    }

    public ObservableCollection<MyObject> MyList
    {
        get { return mMyList; }
        set 
        { 
            // Some function that updates the value and implements INPC
            SetProperty("MyList", ref mMyList, value); 
        }
    }

    public ICommand MyDeleteCommand
    {
        get { return mMyDeleteCommand; }
    }

    void DeleteHandler(var item)
    {
        int index = mMyList.Remove(item);
    }


}

Are the items unique? If so, you could pass the item, and the Delete command handler could look up the item in the list.

If the items are non-unique, you'll have to do a little more logic, depending on the expected outcome.

Now, in the view, your code will look like (notice the StaticResource becomes a Binding):

<ItemsControl ItemsSource="{Binding MyList}">
    <ItemsControl.ItemTemplate>
        ...
            <Button Command="{Binding DataContext.MyDeleteCommand,
                              RelativeSource={RelativeSource FindAncestor,
                                              AncestorType={x:Type ItemsControl}}}" 
                    CommandParameter="{Binding}">
                Remove item
            </Button>
        ...
    </ItemsControl.ItemTemplate>
</ItemsControl>
Wonko the Sane
I don't have a reference to `mMyList` in code. There are multiple such lists displayed in the view.
Heinzi
Sorry - I missed that the Command is a StaticResource. In general, in an MVVM solution [it is my understanding that] Commands are handled in the ViewModel, not in the View. I will update my answer to reflect this.
Wonko the Sane
Thanks! Yes, putting the command into the list's viewmodel was indeed the way to go. I accepted Arthur's solution, since he was a bit faster, but yours was extremely helpful as well!
Heinzi