문제

I have a list of items in a WPF ListBox. I want to allow the user to select several of these items and click a Remove button to eliminate these items from the list.

Using the MVVM RelayCommand pattern, I've created a command with the following signature:

public RelayCommand<IList> RemoveTagsCommand { get; private set; }

In my View, I wire up my RemoveTagsCommand like this:

<DockPanel>
<Button DockPanel.Dock="Right" Command="{Binding RemoveTagsCommand}" CommandParameter="{Binding ElementName=TagList, Path=SelectedItems}">Remove tags</Button>
<ListBox x:Name="TagList" ItemsSource="{Binding Tags}" SelectionMode="Extended">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
    <ListBox.Resources>
        <DataTemplate DataType="{x:Type Model:Tag}">
            ...
        </DataTemplate>
    </ListBox.Resources>
</ListBox>
</DockPanel>

My ViewModel constructor sets up an instance of the command:

RemoveTagsCommand = new RelayCommand<IList>(RemoveTags, CanRemoveTags);

My current implementation of RemoveTags feels clunky, with casts and copying. Is there a better way to implement this?

    public void RemoveTags(IList toRemove)
    {
        var collection = toRemove.Cast<Tag>();
        List<Tag> copy = new List<Tag>(collection);

        foreach (Tag tag in copy)
        {
            Tags.Remove(tag);
        }
    }
도움이 되었습니까?

해결책

This looks fairly clean to me, though you might be able to bind SelectedItems to a property on your VM using Mode=OneWayToSource and then use the bound collection property from RemoveTags.
I'm not entirely sure, but you may be able to use a strongly-typed IList collection in this case.

다른 팁

I'd use the ItemContainerStyle on the ListBox to bind the items' IsSelected property to a flag in the Model (not View Model), e.g.:

 <ListBox.ItemContainerStyle> 
    <Style TargetType="{x:Type ListBoxItem}">  
      <Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>  
    </Style> 
 </ListBox.ItemContainerStyle>

Then you don't need to worry about what argument you're passing in to your command. Also, in my experience when it's simple for an object in a view model to know that the user selected it, you find other uses for that information.

The code in the command would be something like:

foreach (Tag t in Tags.Where(x => x.IsSelected).ToList())
{
   Tags.Remove(t);
}

Why don't you specify the type argument of the RelayCommand to be a List<Tag>, since that is what you are going to get anyway? There is no point specifying a more generic type than that because the executed handler is hard-coded to work with a list of Tag objects. Since you've already made the dependency there, you might as well make it on the type argument, as well. Then your executed handler won't need any cast or copying.

1.) Bind your Remove button a Command in your ViewModel.

2.) When you set up your binding, use a CommandParameter to take the Selecteditems from your ListBox by giving your ListBox a name and using ElementName=NameOfListBox, Path=SelectedItems

3.) Make sure your Command in your ViewModel passes along the arguments. You'll get an object that you can cast as an IList.

Below is a simple example, this should help you set up your structure.

In the View:

<Button Command="{Binding CommandInViewModelForRemove}"
        CommandParameter="{Binding ElementName=blah,Path=SelectedItems}"

<ListBox x:Name="blah" .... />

In the ViewModel:

public ViewModel(){
    RemoveCommand = new RelayCommand<object>(Remove, CanRemove);
}

private void Remove(object selectedItems){
   var list = (IList)selectedItems;
   //do some work, cast to view models that represent list items, etc
}

Hope this helps!

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top