Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF: Command parameter for a delete command in a list

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...

like image 320
Heinzi Avatar asked Aug 13 '10 12:08

Heinzi


2 Answers

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>
like image 169
Arthur Avatar answered Nov 15 '22 08:11

Arthur


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>
like image 30
Wonko the Sane Avatar answered Nov 15 '22 08:11

Wonko the Sane