Kirk Broadhurst
Kirk Broadhurst

Reputation: 28718

Pattern for deleting items from a Silverlight ItemsControl

I have a Silverlight page that contains an ItemsControl. It looks something like this

-- Name             Description          [Add]
-- Thing1           The first thing      [Edit] [Delete]
-- Thing2           The second thing     [Edit] [Delete]

where [Edit], [Delete], and [Add] are buttons.

Currently I'm binding the control to a collection of Thing and using a template to display the properties, and bind to an Edit Command in my ViewModel.

It doesn't make sense (to me) for the ThingViewModel to have a Delete Command which causes it to delete itself;

So what's the best pattern to wire up the [Delete] button?

Upvotes: 0

Views: 1171

Answers (3)

Kirk Broadhurst
Kirk Broadhurst

Reputation: 28718

Here's what I've come up with - in a very simplified form. It's an obvious choice when RelativeSource is not available.

  • Name a control which binds to the parent / collection
  • Specify the ElementName property on the binding for the child / item

This works for an ItemsControl but I haven't been able to get this pattern to work for a DataGrid yet.

<ItemsControl Name="MyParentControl"
              ItemsSource="{Binding Things}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Content="Delete" 
                    Command="{Binding ElementName=MyParentControl, 
                        Path=DataContext.DeleteCommand}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Upvotes: 1

Anton
Anton

Reputation: 7719

If you use a listbox so that the user can select a row and then put your commands in the VM that contains the collection, you get a cleaner implementation.

here is the xaml for a row with a delete button. The trick is to bind the button to the parent items DeleteCommand by using "RelativeSource"

    <ListBox ItemsSource="{Binding MyItems, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding MySelectedItem, UpdateSourceTrigger=PropertyChanged}" >
        <ListBox.ItemContainerStyle>
            <Style TargetType="{x:Type ListBoxItem}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ListBoxItem}">
                            <StackPanel Orientation="Horizontal"   >
                                <Label Content="{Binding}" />
                                <Button Content="Delete" Command="{Binding DataContext.MyDeleteCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}}" />
                            </StackPanel >
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>

And the ViewModel code

public DelegateCommand<object> MyDeleteCommand { get; set; }

protected void DoDelete(object o)
{
    MyItems.Remove(MySelectedItem);
}

protected bool CanDoDelete(object o)
{
    return MySelectedItem != null;
}

string _mySelectedItem;
public string MySelectedItem
{
    get { return _mySelectedItem; }
    set
    {
        _mySelectedItem = value;
        OnPropertyChanged("MySelectedItem");
        MyDeleteCommand.RaiseCanExecuteChanged();
    }
}

ObservableCollection<string> _myItems;
public ObservableCollection<string> MyItems
{
    get { return _myItems; }
    set 
    { 
        _myItems = value;
        OnPropertyChanged("MyItems");
    }
}

oh, i just saw the above comment about silverlight and relativeSource. I did this with WPF and tested it out, which worked, it may not work for silverlight

Upvotes: 0

EtherDragon
EtherDragon

Reputation: 2698

The "Delete" code will not be run on the ViewModel for the individual item, in the collection, rather it would be bubbled up (somehow) to the ViewModel that contains the collection, and processed there.

Pseudocode:

public class ItemContainerViewModel
{
    List<ItemClass> Items { get; set; }

    public void DeleteItem(ItemClass item)
    {
        this.Items.Remove(item);
        NotifyOfPropertyChange(() => this.Items); // Causes controls bound to this list to refresh their contents
    }
}

One way to bubble up the event is to have the ItemViewModel aware of it's parent ViewModel

public class ItemViewModel
{
    public ItemsCollectionViewModel ParentViewModel { get; private set; }
    public ItemViewModel(ItemsCollectionViewModel parentViewModel)
    {
        this.ParentView = parentViewModel;
    }

    public void Delete()
    {
        this.ParentViewModel.Delete(this);
    }
}

There are better ways with a MVVM framework like Caliburn.Micro or MVVM-Lite, but this at least gets you started on how to think about these kinds of operations in your ViewModel.

Essentialy - your ViewModel should be able to do all of your user operations without requiring a View of any kind. (You should be able to run some test code, and have your VM work as intended without a bound View)

Upvotes: 1

Related Questions