Context Actions command binding with parameter

PedroNeves.7715PedroNeves.7715 USMember ✭✭
edited January 2016 in Xamarin.Forms

Hello,

I am trying to implement a swipe to delete:

                  <ViewCell.ContextActions>
                    <MenuItem Command="{Binding Delete}"
                              CommandParameter="{Binding .}"
                              Text="{i18n:Translate Delete}"
                              IsDestructive="True" />
                  </ViewCell.ContextActions>

But, being it a list of MyObject, the output says that Delete was not found in MyObject. But it should be in the viewmodel.
How can i fix this?

Thanks!

Best Answer

Answers

  • PedroNeves.7715PedroNeves.7715 USMember ✭✭

    Great answer, thank you very much!

  • Do you know how to do this in code? I've tried the following line but it returns null.

    deleteButton.SetBinding (Button.CommandParameterProperty, new Binding ("."));
    deleteButton.SetBinding (Button.CommandProperty, new Binding ("BindingContext.DeleteCommand", source: new PlaceRankPage ()));
    
  • MarcoMarinangeliMarcoMarinangeli USUniversity ✭✭

    @GeraldVersluis You really made my day!!

  • SujaBSujaB USUniversity ✭✭✭

    @GeraldVersluis Thank you so much. It worked well for me.

  • SujaBSujaB USUniversity ✭✭✭

    @GeraldVersluis : I crossed through another issue.On clicking the button in the list view i need to pass two parameters(Item id and quantity of the corresponding row in the list) to the view model.I used command parameter property but i could pass only one parameter.How can i pass these parameters to the view model??

  • GeraldVersluisGeraldVersluis NLUniversity ✭✭✭✭

    @SujaB Can't you create a containing object which has those two values as properties? :smile:

  • SujaBSujaB USUniversity ✭✭✭

    @GeraldVersluis : I have some confusion

    <
    ListView x:Name="lineItemListView" RowHeight="{StaticResource rowHeight}" ItemsSource="{Binding LineItemList}">
    <ListView.ItemTemplate>


                <StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
                  <Label Text="{Binding ItemId}" HorizontalOptions="FillAndExpand"></Label>
    
                </StackLayout>
    
    
    
                <StackLayout Orientation="Horizontal">
    
                  <Button WidthRequest="55" x:Name="barcodeButton"  Command="{Binding Path=BindingContext.BarcodeCommand, Source={x:Reference Name=ReceiveLineItemPage}}" CommandParameter="{Binding ItemId}"  HorizontalOptions="End"  HeightRequest="35" Image="ic_barcode_active.png" BackgroundColor="Transparent"></Button>
                  <Button WidthRequest="35" HorizontalOptions="End" HeightRequest="20" BackgroundColor="#ff0000" Text="{Binding LotQuantity}" TextColor="#ffffff"></Button>                  
                </StackLayout>              
              </StackLayout>
    
            </ViewCell>
          </DataTemplate>
        </ListView.ItemTemplate>
      </ListView>
    

    Here i need to bind itemid and lot quantity to barcode button.If i use an object how will it get the value of the corresponding row which the button clicked.

  • GeraldVersluisGeraldVersluis NLUniversity ✭✭✭✭

    Try {Binding .} in your CommandParameter then you get the whole object that is behind it. In the code-behind you can access the ItemId and LotQuantity properties.

  • SujaBSujaB USUniversity ✭✭✭

    @GeraldVersluis Thank you so much.It is working :)

  • GeraldVersluisGeraldVersluis NLUniversity ✭✭✭✭

    Glad to hear! Happy coding!

  • QuentinDujardinQuentinDujardin BEMember
    edited July 2016

    It seems your solution doesn't work (anymore?) when you use a DataTemplate in the ListView ..

    <ListView x:Name="UserLists" ItemsSource="{Binding UserLists}"
                    SeparatorVisibility="Default" SeparatorColor="{x:Static local:Colors.LightSeparator}">
            <ListView.ItemTemplate>
              <DataTemplate>
                <ViewCell>
                  <Grid Padding="4">
                    <Grid.ColumnDefinitions>
                      <ColumnDefinition Width="Auto" />
                      <ColumnDefinition Width="*" />
                      <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <Image VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand" Source="{Binding Selected, Converter={StaticResource SelectedConverter}}"/>
                    <Label Grid.Column="1" Text="{Binding list_lib}" VerticalOptions="CenterAndExpand" VerticalTextAlignment="Center"/>
                    <Button Grid.Column="2" Text="->" x:Name="Information"
                            Command="{Binding Path=BindingContext.InformationCommand, Source={x:Reference Name=Page}}"
                            CommandParameter="{Binding list_id}" VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand"/>
                  </Grid>
                </ViewCell>
              </DataTemplate>
            </ListView.ItemTemplate>
          </ListView>
    

    "InformationCommand" is underline in blue and I have the message "not found" :(

  • batmacibatmaci DEMember ✭✭✭✭✭

    @PedroNeves.7715 said:
    Hello,

    I am trying to implement a swipe to delete:

    how do you do a swipe to delete? is it standard hold the delete or like email app real swipe to delete? if 2nd one, can you please share your experience?

  • batmacibatmaci DEMember ✭✭✭✭✭

    @QuentinDujardin said:
    It seems your solution doesn't work (anymore?) when you use a DataTemplate in the ListView ..


    <ListView.ItemTemplate>



    <Grid.ColumnDefinitions>



    </Grid.ColumnDefinitions>

    "InformationCommand" is underline in blue and I have the message "not found" :(

    because you arent using contextmenu action. you try to achieve this with a button. google it

  • batmacibatmaci DEMember ✭✭✭✭✭

    @GeraldVersluis said:
    Try {Binding .} in your CommandParameter then you get the whole object that is behind it. In the code-behind you can access the ItemId and LotQuantity properties.

    how do you access this in viewmodel? Can you give an example? I tried to bind an object which is type of list view itemsource but it is always null

  • batmacibatmaci DEMember ✭✭✭✭✭

    @batmaci said:

    @GeraldVersluis said:
    Try {Binding .} in your CommandParameter then you get the whole object that is behind it. In the code-behind you can access the ItemId and LotQuantity properties.

    how do you access this in viewmodel? Can you give an example? I tried to bind an object which is type of list view itemsource but it is always null

    If anyone looks for solution for this. it will be as below. ItemType is the type of your item in the list

      public Command OnDeleteClick
            {
                get
                {
                    return new Command<ItemType>(async (obj) =>
                    {
    
                        await DeleteData(obj);            
                    });
                }
            }
    
  • batmacibatmaci DEMember ✭✭✭✭✭
    edited July 2016

    @QuentinDujardin said:
    It seems your solution doesn't work (anymore?) when you use a DataTemplate in the ListView ..


    <ListView.ItemTemplate>



    <Grid.ColumnDefinitions>



    </Grid.ColumnDefinitions>

    "InformationCommand" is underline in blue and I have the message "not found" :(

    I agree with you even using like below will return an error message saying "System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary."
    Anybody has Idea what the problem is?

    EDIT: for the case below solution from @GeraldVersluis didnt work, it was throwing error above but using solution suggested here worked. otherway around Gerald's solution worked for TextView better. I dont know why but happy end :)

     <StackLayout   VerticalOptions="FillAndExpand">
            <ListView x:Name="listItems"   ItemsSource="{Binding Items}"    SelectedItem="{Binding SelectedItem, Mode=TwoWay}"  CachingStrategy="RecycleElement" VerticalOptions="FillAndExpand" HasUnevenRows="True" >
              <ListView.ItemTemplate>
                <DataTemplate>           
                  <ViewCell x:Name="viewCell">
                    <ViewCell.ContextActions>
                      <MenuItem   Command="{Binding Path=BindingContext.OnDeleteClick, Source={x:Reference Name=myPage}}"
                         CommandParameter="{Binding .}"     
                         Text="Delete" IsDestructive="True" />" 
                    </ViewCell.ContextActions>
    
  • GeraldVersluisGeraldVersluis NLUniversity ✭✭✭✭

    Good to see you worked it out :smile:

  • jk8jk8 USMember

    Thank you all... I was trying to get the passed parm from a MenuItem command in a XAML ListView named ( x:Name="fxListView" ) into my ViewModel Command method (just a bit different than WPF I am used to)

    (in my XAML)
    <MenuItem Command="{Binding Path=BindingContext.TestCommand, Source={x:Reference Name=fxListView}}" Text="Add" CommandParameter="{Binding .}"/>

    And this worked in my view model (thank you especially batmaci your sample unlocked the knowledge for me)

    (in viewmodel constructor)
    TestCommand = new Command<Transaction>((TestCommandParameter) => TestCommand_Execute(TestCommandParameter));

    (in viewmodel class)
    public ICommand TestCommand { get; set; } public Transaction TestCommandParameter { get; set; }

    private void TestCommand_Execute(Transaction obj)

    Bottom line is I was missing putting the TestCommandParameter in both places on the line in my constructor,
    hope this example helps. FWIW also this is not my final usage and my look bad but I am so glad I got it to finally work/talk ...
    It passes in the object that I long click/hold on into TestCommand_Execute perfectly, joy...
    thank you all

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    @GraemeSutters said:
    Do you know how to do this in code? I've tried the following line but it returns null.

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

    @GraemeSutters have you found a solution?
    @GeraldVersluis can you take a look? thanks ;)

  • GeraldVersluisGeraldVersluis NLUniversity ✭✭✭✭

    So, the command gets invoked but the parameter is null?

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    @GeraldVersluis ops... it works. Thanks bro.

  • GeraldVersluisGeraldVersluis NLUniversity ✭✭✭✭

    @AlessandroCaliaro said:
    @GeraldVersluis ops... it works. Thanks bro.

    Haha no worries!

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    @GeraldVersluis I have another little problem.
    I have this code in my ViewCell

    Label lTrash = new Label { Text = "Trash"};
    TapGestureRecognizer tgrTrash = new TapGestureRecognizer();
    tgrTrash.SetBinding(TapGestureRecognizer.CommandProperty,new Binding("BindingContext.TrashCommand", source: new MyPage()));
    tgrTrash.SetBinding(TapGestureRecognizer.CommandParameterProperty, ".");
    

    When I tap on this lTrash (one for every row in my ListView), the Command is reached in my ViewModel

    this.TrashCommand = new Command(async (object obj) => {
    
        try
        {
            var ret = await Application.Current.MainPage.DisplayAlert("Attention", "Delete this row?", "Yes", "No");
    
            if (ret)
            {
    
                int idx = List.IndexOf((Model)obj);
    
                List.Remove((Model)obj);
            }
    
            _isTapped = false;
    
        }
        catch (Exception ex) {
            await Application.Current.MainPage.DisplayAlert("Attention", ex.Message, "Ok");
        }
    });
    

    In "obj" I have my "Model" and I would like to remove this object from my List, but IndexOf is always -1...

    Can't I use List.IndexOf to find my "obj"?

  • GeraldVersluisGeraldVersluis NLUniversity ✭✭✭✭

    How do you fill your listview? What are the objects in there?

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    My Model

    using System;
    using PropertyChanged;
    namespace TestBinding
    {
        [ImplementPropertyChanged]
        public class Model
        {
            public Model ()
            {
            }
    
            public string Description { get; set; }
            public double Cost { get; set; }
            public int Qty { get; set; }
    
        }
    }
    
  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    My ViewModel

    using System;
    using System.Collections.ObjectModel;
    using PropertyChanged;
    using Acr.UserDialogs;
    using System.Threading.Tasks;
    using Xamarin.Forms;
    using System.Windows.Input;
    
    namespace TestBinding
    {
    
        [ImplementPropertyChanged]
        public class MyPageViewModel
        {
            bool _isLabelEmptyVisible { get; set; }
            Model _selectedItem { get; set; }
            bool _isTapped { get; set; }
    
            public ObservableCollection<Model> List { get; set; } = new ObservableCollection<Model>();
    
            public MyPageViewModel()
            {
                List.Add(new Model { Description = "D1", Cost = 10.0, Qty = 1 });
                List.Add(new Model { Description = "D2", Cost = 20.0, Qty = 2 });
                List.Add(new Model { Description = "D3", Cost = 30.0, Qty = 3 });
    
                this.TrashCommand = new Command(async (object obj) => {
    
                    try
                    {
                        if (_isTapped)
                            return;
    
                        if (obj != null)
                            System.Diagnostics.Debug.WriteLine("Obj is not null");
                        else
                            System.Diagnostics.Debug.WriteLine("Obj IS null");
    
    
                        _isTapped = true;
                        var ret = await Application.Current.MainPage.DisplayAlert("Attention", "Delete this row?", "Yes", "No");
    
                        if (ret)
                        {
    
                            int idx = List.IndexOf((Model)obj);
    
                            List.Remove((Model)obj);
                        }
    
                        _isTapped = false;
    
                    }
                    catch (Exception ex) {
                        _isTapped = false;
                        await Application.Current.MainPage.DisplayAlert("Attention", ex.Message, "Ok");
                    }
                });
            }
    
            public bool IsLabelEmptyVisible { 
                get { return _isLabelEmptyVisible; }
                set {
                    _isLabelEmptyVisible = List.Count == 0;
                }
            }
    
            public bool IsListViewVisible { 
                get { return !IsLabelEmptyVisible; }
            }
    
            public Model SelectedItem { 
                get { return _selectedItem; }
                set {
                    _selectedItem = value;
    
                    if (_selectedItem != null) {
    
                        //Task.Run(async () =>
                        //{
                            Device.BeginInvokeOnMainThread(async() =>
                            {
                                var ret = await Application.Current.MainPage.DisplayActionSheet("Select", "Cancel", "Destruction", new string[] { "Edit", "Delete" });
    
    
                                if (ret == "Edit")
                                {
    
                                    PromptConfig promptConfig = new PromptConfig();
                                    promptConfig.CancelText = "CANCEL";
                                    promptConfig.InputType = InputType.Number;
                                    promptConfig.Message = "Modify QTA";
                                    promptConfig.OkText = "OK";
                                    promptConfig.Title = "UPDATE";
                                    PromptResult result = await UserDialogs.Instance.PromptAsync(promptConfig);
                                    if (result.Ok)
                                        SelectedItem.Qty = int.Parse(result.Value);
    
                                }
                                else if (ret == "Delete")
                                    List.Remove(SelectedItem);
                                else { }
                            });
                        //});
                    }
                }
            }
    
            public ICommand TrashCommand { get; protected set;}
        }
    }
    
  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    For Example, if I press the label Trash in the first row, "obj" has the correct D1 / 10 / 1 values for Description, Cost and Qty, but List.IndexOf(obj) return -1 (List[0].Description is "D1", etc....)

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    https://github.com/acaliaro/TestBindingWithListView

    @GeraldVersluis this is the demo prj..if you want to take a look.
    Maybe a XF bug?

    in ViewModel you find 2 Commands

    QTY Command modify correctly the Obj parameter
    TRASH Command don't

    Thanks

  • GeraldVersluisGeraldVersluis NLUniversity ✭✭✭✭

    It's very weird. The objects do not appear to be the same thing (in memory). You could delete the item with something like this:

    var trashedItem = List.SingleOrDefault (i => i.Description == ((Model)obj).Description);
    if (trashedItem == null)
    

    return; // TODO do something useful

    //int idx = List.IndexOf((Model)obj);
    List.Remove (trashedItem);
    Count = List.Count;
    

    But that shouldn't be necessary.
    Also, by doing this it gets deleted from the list, but the UI is not updated. So it seems something isn't going right.

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    Yes @GeraldVersluis , your it's a good workaround but the problem persists..
    The strange fact is that the other command (that works in the same way... QtyCommand...) works as exptected.
    I don't know if write somethig to xamarin.bugzilla.com

  • SHAKTIMANSHAKTIMAN INMember ✭✭

    Can anyone please help me to perform operation on Button which is inside the listview
    and how would i know on which selected item the button is clicked

  • BsaferBsafer USMember ✭✭

    This works well for me for MVVM. I was stuck with the commandparameter. So MenuItem inside a listview returning the list item back to the Command.

    Name the content page
    x:Name="CAGroupMembersViewPage"

        <MenuItem Text="Remove" CommandParameter="{Binding .}"
                  Command="{Binding Path=BindingContext.RemoveSingleClicked, Source={x:Reference CAGroupMembersViewPage} }" 
                    IsDestructive="True" />
    

    In Codebehind
    public ICommand NotifySingleClicked { protected set; get; }
    In Codebehind Constructor
    NotifySingleClicked = new Command<Members>(async (key) => { _userDialogs.Alert("Notify Clicked" + key.Id); });

  • I'm just going to write this because no one mentioned it on this thread.

    The other option (which does the same thing as GeraldVersluis suggested is to declare the binding context of the MenuItem:

    <MenuItem Text="Delete" IsDestructive="True" Command="{Binding DeleteCommand}" CommandParameter="{Binding .}">
        <MenuItem.BindingContext>
            <vm:MyPageViewModel/>
        </MenuItem.BindingContext>
    </MenuItem>
    
Sign In or Register to comment.