Calling Command From ViewModel Using Button Within ListView

I've made a custom ViewCell with a button in it to delete the row of the ListView. It has a Clicked method that obtains the item from the ListView that I want to delete but I can't work out how to call the Delete Command I've made in my ViewModel. How do I go about joining the pieces I have together? I'm pretty new to Xamarin so I fear I'm missing something obvious.

I've put the relevant bits of code from the three classes below, including the ContentPage with the ListView itself.

    public class PlaceListCell : ViewCell
        {
            public PlaceListCell ()
            {
                var deleteButton = new Button {
                    Text = "Delete"
                };
                deleteButton.SetBinding (Button.CommandParameterProperty, new Binding ("."));
                deleteButton.Clicked += (sender, e) => 
                {
                    var b = (Button) sender;
                    var item = (Place) b.CommandParameter;

                }; 

            }
        }

        public class PlaceRankPage : ContentPage
        {
            PlaceRankVM _vm {
                get { return BindingContext as PlaceRankVM; }
            }

            public PlaceRankPage ()
            {
                var placeList = new ListView {
                    ItemTemplate = placeTemplate
                };
                placeList.SetBinding (ListView.ItemsSourceProperty, "Places");

                placeList.ItemTapped += async (sender, e) => {
                    var item = (Place) e.Item;
                    _vm.ViewCommand.Execute (item);
                    placeList.SelectedItem = null;
                };
            }
        }

        public class PlaceRankVM : ViewModel
        {
            ObservableCollection<Place> _places;
            public ObservableCollection<Place> Places
            {
                get { return _places; }
                set {
                    _places = value;
                    OnPropertyChanged ();
                }
            }

            Command<Place> _deleteCommand;
            public Command<Place> DeleteCommand {
                get { return _deleteCommand
                    ?? (_deleteCommand = new Command<Place> (async (place) => await 
                        ExecuteDeleteCommand (place)));
                }
            }

            async Task ExecuteDeleteCommand(Place place)
            {
                Places.Remove (place);
            }                   
        }

Best Answer

Answers

  • SapienDeveloperSapienDeveloper USMember ✭✭

    when you bind something in a viewcell, you are binding to the BindingContext of the viewcell. So in your case each ViewCell is bound to a "Place". This includes commands as well.
    If you had a Delete Command defined in your "Place" class it would call that.

    For something like what you are trying to do I think you might be able to set up a binding for your delete button with a source pointing back to the bindingcontext of your ContentPage.

    so something like the answer provided in the thread below might work.

    https://forums.xamarin.com/discussion/58709/context-actions-command-binding-with-parameter#latest

  • That is what I'm trying to do but I'm not sure how to do it in code, all the examples I've found use Xaml.

    I essentially want to use the following code, as I did when the ViewCells themselves are tapped, but I can't work out how to get at the BindingContext of my ContentPage (vm) from the PlaceListCell class.

    deleteButton.Clicked += (sender, e) => 
                {
                    var b = (Button) sender;
                    var item = (Place) b.CommandParameter;
                    vm.DeleteCommand.Execute (item);
                }; 
    
  • JulienRosenJulienRosen CAMember ✭✭✭✭
    edited February 2016

    i think you are mixing click event and commands

    you should be using Command and CommandParameter together

    click event = code behind
    command + commandparameter = mvvm/binding

    deleteButton.Command = ExistingICommand;

  • @JulienRosen I'm afraid I don't quite follow. My 'ExistingICommand' (DeleteCommand) is in my ViewModel so how do I attach that to my deleteButton?

  • JulienRosenJulienRosen CAMember ✭✭✭✭

    deleteButton.SetBinding(MenuItem.CommandProperty, new Binding("DeleteCommand"));

  • @JulienRosen I've tried a couple of variations of your suggestion but the binding properties aren't being found.

    deleteButton.SetBinding (Button.CommandParameterProperty, new Binding ("."));
    deleteButton.SetBinding (Button.CommandProperty, new Binding ("DeleteCommand"));
    

    gives the following in the Application Output

    Binding: 'DeleteCommand' property not found on 'TripPlan.Models.Place', target property: 'Xamarin.Forms.Button.Command'

    while the following that I put together from the link above

    deleteButton.SetBinding (Button.CommandParameterProperty, new Binding ("."));
    deleteButton.SetBinding (Button.CommandProperty, new Binding ("DeleteCommand", source: new PlaceRankPage ()));
    

    gives the following output

    Binding: 'DeleteCommand' property not found on 'TripPlan.Views.PlaceRankPage', target property: 'Xamarin.Forms.Button.Command'

    So i need to be able to connect to the BindingContext of the PlaceRankPage (the ViewModel). I tried

    new Binding ("DeleteCommand", source: new PlaceRankPage ().BindingContext)

    but that gave the first error again. Any suggestions?

  • JulienRosenJulienRosen CAMember ✭✭✭✭
    edited February 2016

    Binding: 'DeleteCommand' property not found on 'TripPlan.Models.Place', target property:

    this means that your Button has a BindingContext of TripPlan.Models.Place

    so its looking for DeleteCommand on TripPlan.Models.Place

    if that isn't correct, you'll have to give your button the appropriate data context, or do something like
    deleteButton.SetBinding (Button.CommandProperty, new Binding ("InstanceOfViewModel.DeleteCommand"));

    where is InstanceOfViewModel is a property of the BindingContext of the button that contains a DeleteCommand property.

    really, i would highly recommend writing actual xaml with backing view models, as it makes this sort of thing much more clear.

  • @JulienRosen I've never actually used xaml before. Would t be possible to just write the custom ViewCell in xaml and combine it with the rest of the parts that are written in code as I don't have time to redo the whole thing?

  • JulienRosenJulienRosen CAMember ✭✭✭✭
    edited February 2016
    public class ListViewPage() : Page
    {
       public ListViewPage()
       {
          BindingContext = new MyViewModel()
       }
    }
    
        <ListView ItemTemplate="{StaticResource MyCustomCell}" ItemsSource="{Binding MyItems}" />
    
              <DataTemplate x:Key="MyCustomCell">
                <ViewCell>
                  <Button Command="{Binding CmdDeleteCommand} />
                </ViewCell>
              </DataTemplate>
    
    
        public class MyViewModel()
        {
           public ObservableCollection<ItemViewModel> MyItems {get;set;}
        }
    
        public class ItemViewModel()
        {
           public ICommand CmdDeleteCommand { get; set; }
        }
    
  • SapienDeveloperSapienDeveloper USMember ✭✭

    @GraemeSutters I'm not sure if you can create a separate XAML class just for a ViewCell, but you should be able to make a ContentView with XAML and then create a Code based ViewCell with a link to the XAML content view.

    As shown in the thread below

    https://forums.xamarin.com/discussion/20409/creating-a-custom-viewcell-in-xaml

  • After re-reading the link in the first reply I've managed to get rid of the 'Binding property not found' errors but now I get a System.NullReferenceException whenever I press the button. Is there anything left I could have missed?

    deleteButton.SetBinding (Button.CommandParameterProperty, new Binding ("."));
    deleteButton.SetBinding (Button.CommandProperty, new Binding ("BindingContext.DeleteCommand", source: new PlaceRankPage ()));
    deleteButton.Clicked += (sender, e) => 
    {
        var b = (Button) sender;
        var item = (Place) b.CommandParameter;
        Debug.WriteLine("Clicked button: " + item.Name);
        deleteButton.Command.Execute (item);
    }; 
    
  • SapienDeveloperSapienDeveloper USMember ✭✭

    if you are binding to the DeleteCommand you dont need to do the execute step in the clicked process. infact remove the clicked event all together.

  • I took them out but now the button doesn't do anything.

  • @SapienDeveloper Perfect! That's just the job. Cheers.

Sign In or Register to comment.