Jacob Alley
Jacob Alley

Reputation: 834

Dynamically removing an item from an observable collection - WPF

I have a listview in a wpf that gets populated by a sql query. I created a class called "Carrier", and defined it like so.

public class Carrier
    {
        public Carrier(string rank, string id, string name, string add1, string add2, string add3, string add4)
        {
            _rank = rank + ".";
            _carrierId = id;
            _carrierName = name;
            _address1 = add1;
            _address2 = add2;
            _address3 = add3;
            _address4 = add4;
            OnRemoveClick = new DelegateCommand<string>(RemoveCarrierFromNode);
        }
}

I omitted all of the declarations of the variables, for the sake of space.

My wpf looks like this:

enter image description here

Each "Node" has a different amount of carriers, and depending on the selected node, the carrier list changes accordingly. The list of carriers is binded to an observablecollection, named CarrierList.

Here is the XAML for my Listview that shows how the dynamic labels, and buttons are formed.

<ListView Margin="134,110,100,50" HorizontalAlignment="Center" Width="232" BorderBrush="LightGray" BorderThickness="1,1,1,1" Visibility="{Binding Path=ShowCarrierList, Converter={StaticResource BoolToVis}}" ItemsSource="{Binding CarrierList}" Grid.RowSpan="2" ScrollViewer.CanContentScroll="True">
            <ListView.ItemTemplate>
                <DataTemplate>
                        <DockPanel Margin="0,0,0,0" HorizontalAlignment ="Stretch">
                            <Grid HorizontalAlignment="Stretch">
                                <Grid.Resources>
                                    <Style TargetType="{x:Type Border}">
                                        <!-- All rows -->
                                        <Setter Property="BorderBrush" Value="Black" />
                                        <Setter Property="BorderThickness" Value="2" />
                                        <Setter Property="CornerRadius" Value="5" />
                                    </Style>
                                </Grid.Resources>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="165" />
                                    <ColumnDefinition Width="20" />
                                    <ColumnDefinition Width="20" />
                                    <ColumnDefinition Width="20" />
                                </Grid.ColumnDefinitions>
                            <TextBlock Text="{Binding Rank}"  />
                            <TextBlock Text="{Binding CarrierName}" Margin="13,0,0,0"/>


                            <Button  Width="20" Grid.Column="1" HorizontalAlignment="Right" ToolTip="Edit Carrier Details">
                                    <StackPanel>
                                        <Image Source="{StaticResource EditImg}"/>
                                    </StackPanel>
                                </Button>
                                <Button  Width="20" Grid.Column="2" HorizontalAlignment="Right" ToolTip="View All Nodes With This Carrier">
                                    <StackPanel>
                                        <Image Source="{StaticResource NetworkImg}"/>
                                    </StackPanel>
                                </Button>
                            <Button  Width="20" Grid.Column="3" HorizontalAlignment="Right" ToolTip="Remove Carrier From Node" Command="{Binding OnRemoveClick}" CommandParameter="{Binding CarrierName}">
                                    <StackPanel>
                                        <Image Source="{StaticResource ErrorImg}"/>
                                    </StackPanel>
                            </Button>
                            </Grid>
                        </DockPanel>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

When I click the "X" button, the command that gets triggered has to be inside of the Carrier object. I tried declaring the command in the view model and it was never getting triggered, but when i put it in the Carrier class, I am able to trigger the Remove command. However, I am at a loss on how to remove the item from the collection from inside of the item object.

Upvotes: 2

Views: 1872

Answers (2)

mrsargent
mrsargent

Reputation: 2442

If I understand your question correctly you are trying to click the "X" button to remove the selected item from your list? If so I would use your DelegateCommand with the CommandParameter as follows

<ListView x:Name="carrierList">            
        <Button Command="{Binding ElementName=carrierList,Path=DataContext.OnRemoveClick}" CommandParameter="{Binding}">
            <StackPanel>
                <Image Source="{StaticResource ErrorImg}"/>
            </StackPanel>
        </Button>

    </ListView>

And then remove the item in your viewmodel

 public class MainViewModel
{
    public ICommand OnRemoveClick { get; set; }        

    public MainViewModel()
    {
        OnRemoveClick = new DelegateCommand<Carrier>(RemoveCarrierFromNode);

    }

    public void RemoveCarrierFromNode(object obj)
    {
        CarrierList.Remove(obj);
    }

Upvotes: 1

Emperor Eto
Emperor Eto

Reputation: 3520

This is a pretty common pattern and you only need a few changes to make it work. First, I would indeed make the Remove command part of the View Model. If you don't do this then, as you note, you would somehow need each Carrier item to have a reference to its collection, which would be quite inelegant and could easily lead to memory leaks.

When you bind the Remove button to the Remove command of your view model, you need to specify the full path of the view model as the source, because the DataContext of the ListViewItem is the Carrier not the View Model. One way you could do this is to give your ListView an explicit name (e.g., Name="x:_listView"), then bind as such:

Command="{Binding ElementName=_listView, Path=DataContext.OnRemoveClick}"

That will solve the problem of the view model's command not being fired.

Second, change

CommandParameter="{Binding CarrierName}"

to

CommandParameter="{Binding}"

The parameter of your OnRemoveClick command handler will then be the Carrier object whose button was clicked, which can now be removed simply by saying CarrierList.Remove(arg)

Finally, you need to change:

OnRemoveClick = new DelegateCommand<string>(RemoveCarrierFromNode);

to

OnRemoveClick = new DelegateCommand<Carrier>(RemoveCarrierFromNode);

because you want the command parameter to be the Carrier object, not its name.

Hope this helps!

Upvotes: 1

Related Questions