How to delete an item from a ListView in MVVM

GrinenkoAGrinenkoA USMember ✭✭

Hi, I am new to Xamarin, Forms and MVVM, so this might sound a little dumb, but what is the proper way to delete an item from a ListView? I tried to do it with a ContextAction, but I can't figure a way to know in my ViewModel which item to delete from the ObservableCollection. Binding the SelectedItem of the ListView does not help, since a long tap on Android or a swipe on iOS don't select the item. I use Prism with Unity for MVVM, if this is of any importance. Here's my ListView:

<ListView x:Name="List" ItemsSource="{Binding Users}" HasUnevenRows="True">
    <ListView.Behaviors>
        <b:EventToCommandBehavior EventName="ItemTapped" Command="{Binding SelectUserCommand}" EventArgsConverter="{converters:ItemTappedEventArgsConverter}"/>
    </ListView.Behaviors>
    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell x:Name="Item">
                <ViewCell.ContextActions>
                    <MenuItem Text="Delete" BindingContext="{Binding Source={x:Reference List}, Path=BindingContext}" Command="{Binding DeleteCommand}" CommandParameter="{Binding .}" IsDestructive="True"/>
                </ViewCell.ContextActions>
                <Grid Padding="10">
                    <Label Style="{DynamicResource ListItemTextStyle}" Grid.ColumnSpan="2" Text="{Binding Name}"/>
                    <Label Style="{DynamicResource ListItemDetailTextStyle}" Grid.Row="1" Text="{Binding Login}"/>
                    <Label Style="{DynamicResource ListItemDetailTextStyle}" Grid.Row="1" Grid.Column="1" Text="{Binding Role}"/>
                </Grid>
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

And the corresponding ViewModel (I removed some code to save space):

public class MainPageViewModel : BindableBase, INavigatedAware
{
    //services
    INavigationService _navigationService;
    IPageDialogService _dialogService;

    //fields and props
    private ObservableCollection<User> _users;
    public ObservableCollection<User> Users => _users;

    //commands
    private DelegateCommand _deleteCommand;
    public DelegateCommand DeleteCommand => _deleteCommand ?? (_deleteCommand = new DelegateCommand(DeleteUser));

    //methods
    public MainPageViewModel(INavigationService navigationService, IPageDialogService dialogService)
    {
        _navigationService = navigationService;
        _dialogService = dialogService;

        _users = new ObservableCollection<User>() { new User() { Login = "Login", Name = "Name", Role = "Role" } };
    }

    private void DeleteUser() => Debug.WriteLine("This should delete a user");
}

I would appreciate if anyone could explain me how should I implement the deletion of an item using the MVVM pattern. Thanks

Posts

  • N_BauaN_Baua INMember ✭✭✭✭✭

    Hi @GrinenkoA,

    Hope this helps.

    -- N Baua

  • GrinenkoAGrinenkoA USMember ✭✭

    Hi @N_Baua,
    Unfortunately, it doesn't, 'cause I need to delete an Item on which the user performed the ContextAction, and I need to know which item to delete in ViewModel (pass the Item from the View to the ViewModel somehow). This video showcases the ObservableCollection benefits vs a simple List, and I am already using one. Thank you for your reply anyways.

  • N_BauaN_Baua INMember ✭✭✭✭✭
    edited September 7

    Hi @GrinenkoA,

    Try something like

    <ViewCell.ContextActions>
            <MenuItem Text="Delete" IsDestructive="True" Command="{Binding DeleteCommand}" />
    </ViewCell.ContextActions>
    

    Hope it helps.
    N Baua

  • JohnHardmanJohnHardman GBUniversity ✭✭✭✭✭
    edited September 7

    @GrinenkoA - In code behind, this is what I do:

            var deleteAction = new MenuItem { Text = "Delete", IsDestructive = true };
            deleteAction.Command = new DeleteHandler();
            deleteAction.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));
            this.ContextActions.Add(deleteAction);
    

    where DeleteHandler is a class that implements ICommand.

    When the CanExecute method of DeleteHandler is called, the parameter passed to CanExecute identifies the item to act on.

  • GrinenkoAGrinenkoA USMember ✭✭

    @N_Baua said:
    Hi @GrinenkoA,

    Try something like

    <ViewCell.ContextActions>
            <MenuItem Text="Delete" IsDestructive="True" Command="{Binding DeleteCommand}" />
    </ViewCell.ContextActions>
    

    Hope it helps.
    N Baua

    I already do this too, if you look at my XAML.

    @JohnHardman said:
    @GrinenkoA - In code behind, this is what I do:

            var deleteAction = new MenuItem { Text = "Delete", IsDestructive = true };
            deleteAction.Command = new DeleteHandler();
            deleteAction.SetBinding(MenuItem.CommandParameterProperty, new Binding("."));
            this.ContextActions.Add(deleteAction);
    

    where DeleteHandler is a class that implements ICommand.

    When the CanExecute method of DeleteHandler is called, the parameter passed to CanExecute identifies the item to act on.

    Tank you for the reply, @JohnHardman. The catch is, I can't do anything like that in the code behind, since I use Prism and it hooks the View to the ViewModel automatically. My ObservableCollection and my Commands are in the ViewModel, so I don't see a way to reference the collection from the code behind of the page. And when I try to look at what I receive as the CommandParameter, it's a MainPageViewModel, probably due to the BindingContext change that I do to access the DeleteCommand

  • JohnHardmanJohnHardman GBUniversity ✭✭✭✭✭
    edited September 7

    @GrinenkoA - I don't use Prism currently and I don't do a lot of XAML, but wonder whether you need the following bit or whether you could remove it:

        BindingContext="{Binding Source={x:Reference List}, Path=BindingContext}"
    
  • GrinenkoAGrinenkoA USMember ✭✭
    edited September 7

    @JohnHardman - this is because if I don't, the DeleteCommand is not found, since the binding context of the cell is not the ViewModel anymore, but the Users collection. Right now I'm trying a different approach with a UserManager, I will post here if it works

    UPD: not the Users collection, but a single User

  • JohnHardmanJohnHardman GBUniversity ✭✭✭✭✭
    edited September 7

    @GrinenkoA - Ah, ok. In my code-behind, I have the DeleteHandler in the subclass of ViewCell. That would be one option - to add a subclass of ViewCell, but pehaps there is a more Prismy way of doing it.

    Alternatively, does your DeleteCommand only get triggered after the ListView item being selected? If so, you could presumably make use of the last ItemSelected ?

  • N_BauaN_Baua INMember ✭✭✭✭✭

    I guess you need to use the EventArgsParameterPath="Item" in your b:EventToCommandBehavior , this way you can pass the item as under

            ItemTappedCommand = new DelegateCommand<YourClass>(OnListViewTapCommandExecuted);
    

    and the delegated function can execute this as

    private async void OnListViewTapCommandExecuted(YourClass oClass)
        {
           if(oClass != null){
        //Do something cool
        }
        }
    
  • BrianLagunasBrianLagunas USInsider ✭✭✭
    edited September 7

    It's easy. Just use this snippet. This is what I use in my apps and it works for me.

    <ViewCell.ContextActions>
         <MenuItem Text="Delete" Command="{Binding BindingContext.DeleteItemCommand, Source={x:Reference nameOfList}}" CommandParameter="{Binding .}"/>
    </ViewCell.ContextActions>
    
  • GrinenkoAGrinenkoA USMember ✭✭

    @JohnHardman, @N_Baua - the item is not selected when a context action is performed, as I wrote originally:

    since a long tap on Android or a swipe on iOS don't select the item.

    I was testing an idea I had - a UserManager object declared on the App level that would contain the users collection, and the command would be in the User class, so the UserManager will be called directly from the User to delete the User itself, something like:

    XAML:
    <ViewCell.ContextActions>

    </ViewCell.ContextActions>

    User class:
    public class User
    {
    public string Login { get; set; }
    public string Name { get; set; }
    public string Role { get; set; }

        private DelegateCommand _deleteCommand;
        public DelegateCommand DeleteCommand => _deleteCommand ?? (_deleteCommand = new DelegateCommand(DeleteUser));
    
        public void DeleteUser()
        {
            App.UserManager.Users.Remove(this);
        }
    }
    

    UserManager class:
    public class UserManager
    {
    private ObservableCollection _users;
    public ObservableCollection Users { get => _users; set => _users = value; }

        public UserManager()
        {
            _users = new ObservableCollection<User>() { new User() { Login = "Login", Name = "Name", Role = "Role" } };
        }
    }
    

    MainPageViewModel:
    public ObservableCollection Users => App.UserManager.Users;

    But I believe that this approach is not the one that should be used in MVVM, since I have some logic in the Model...

  • GrinenkoAGrinenkoA USMember ✭✭
    edited September 7

    Hi @BrianLagunas, thank you so much, it works. But only on Android for now, not on Windows, and I didn't test it on iOS yet. On Android and iOS everything seems fine, but on Windows the ViewCell is empty (visually, the object behind is present) and doesn't have any context actions...

    UPD: Tested on iOS, work fine as well

  • BrianLagunasBrianLagunas USInsider ✭✭✭

    @GrinenkoA sounds like a bug in Xamarin.Forms. You should report it to Xamarin... I mean Microsoft :)

  • GrinenkoAGrinenkoA USMember ✭✭

    @BrianLagunas alright, will do so. Anyways, my app won't be on Windows, so it's not a problem for the final product, I just used UWP for faster testing... Thanks again, Prism is really helping me a lot to get started with MVVM and Xamarin :smile:

  • JohnHardmanJohnHardman GBUniversity ✭✭✭✭✭

    @GrinenkoA - As I know others have missed this in the past (and because context menus work for me on UWP) - on UWP, it's a right-click to get the context menu if using a mouse, otherwise a long-press.

  • GrinenkoAGrinenkoA USMember ✭✭

    @JohnHardman - yeah, I know how to use a list in uwp, I did some XAML design before :smile:

Sign In or Register to comment.