How to properly remove Items from a ListView when

2019-09-02 10:15发布

I tried to follow the example here:

WPF ListBox with self-removing items

It made sense but my issue was, the ListView itself is determining the template used. So it can easily customise the bindings to point to the correct target. I am however using MVVM and am struggling to fit the two together.

Example, if the template was:

<ListBox.ItemTemplate>
      <DataTemplate>
          <local:MyItemView/>
      </DataTemplate>
</ListBox.ItemTemplate>

This suddenly becomes more difficult, as ideally, I want to reuse that view without hard coding the bindings.

I tried to use DependencyProperty to pass the List and the Element through, so I could delete it via command.

<ListBox.ItemTemplate Name="myList">
      <DataTemplate>
          <local:MyItemView TheList={Binding ElementName=myList, Path=DataContext.List} TheElement={Binding}/>
      </DataTemplate>
</ListBox.ItemTemplate>

However, I had binding errors telling me that it couldn't convert the value for TheElement from MyClassViewModel to MyClass. Even if I commented that out TheList was always NULL.

Essentially I want:

class MyDataClass { // pretend there's more here}

class MyDataClassContainer
{
    public ObservableCollection<MyDataClass> Items;
    public void Add(MyDataClass);
    public void Remove(MyDataClass);
}

class MyDataClassEntryViewModel
{
    public static readonly DependencyProperty ListItemProperty = DependencyProperty.Register("TheClass", typeof(MyDataClass), typeof(MyDataClassEntryViewModel));
    public static readonly DependencyProperty ListContainerProperty = DependencyProperty.Register("TheContainer", typeof(MyDataClassContainer), typeof(MyDataClassEntryViewModel));

    public MyDataClass TheClass;
    public MyDataClassContainer TheContainer;
    public ICommand Delete = new DelegateCommand(RemoveItem);

    private function RemoveItem(object parameter)
    {
        TheContainer.Remove(TheClass);
    }
}

With the following templates:

MyDataClassEntryView.xaml

<UserControl>
    <Grid>
        <Button Content="Delete" Command="{Binding Path=Delete}"/>
    </Grid>
</UserControl>

MyDataContainerView.xaml

<UserControl>
    <ListView x:Name="listView" ItemsSource="{Binding Path=Container.Items}">
        <ListView.ItemTemplate>
            <DataTemplate>
                <local:MyDataClassEntryView TheClass="{Binding}" TheContainer="{Binding ElementName=listView, Path=DataContext.Container}"/>
            </DataTemplate>
        </ListView.ItemTemplate>
     </ListView>
</UserControl>

Note: I have omitted most of the superfluous lines, as I'm trying to get a generic answer I can use everywhere. Not a hard coded single solution. I was basically want to keep the MVVM structure strong, without lots of hard coded and wiring in the background. I want to use the XAML as much as possible.

All the other methods I see to do with removing from a list, require all sorts of assumptions, such as using the SelectedIndex/Item, or using a method on the ContainerView itself to take the element as a parameter, cast it, then remove, etc. In short, most solutions are far too hard coded to the given examples. It feels like there should be an easy way to achieve this in WPF.

As the ListView issautomatically creating instances of my sub-ViewModel/Views, it's impossible for me to get any data in apparently. I just want to pass parameters along using bindings, basically.

标签: c# wpf mvvm
2条回答
一纸荒年 Trace。
2楼-- · 2019-09-02 11:04

Your button should look like this:

<Button Content="Delete" 
        Command="{Binding Path=Delete}"
        CommandParameter="{Binding}/>

Then the remove command should look something like this:

private function RemoveItem(object parameter)
    {
        var item = parameter as MyDataClass
        if(item != null)
            TheContainer.Remove(item);
    }

You do not need to pass the list to the UserControl within the ItemTemplate, since it doesn't need to know about the list at all

Edit: I read over your question a few times to see what you were confused about so I will try to clarify.

Whether the ListView sets its own template in the Xaml, or you use another UserControl, the datacontext still gets passed down to the item. Regardless of how you decide to template the items, the ItemTemplate will have the datacontext of a single item from the ListView's items list.

I think your confusion comes in with having controls outside being brought in for templating. Think of it as if the Xaml from the control you brought in being cut and pasted into the DataTemplate of the ListView when running the program, and then it is really no different from being hard coded in there.

查看更多
Explosion°爆炸
3楼-- · 2019-09-02 11:12

You cannot reach outside of a DataTemplate with Element bindings like you have tried.

Instead you need to use a relativesource like this.

<local:MyItemView TheList="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}, Path=DataContext.List}" />
查看更多
登录 后发表回答