locked
Context Actions command binding with parameter RRS feed

  • Question

  • User140108 posted

    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!

    Wednesday, January 6, 2016 2:40 PM

Answers

  • User103165 posted

    This is a bit tricky.

    Because you use data-binding you are now looking for the Delete Command on the MyObject, while it is actually in your ViewModel. So you have to tell where it can find the Delete Command.

    Do it like this;

    <ViewCell.ContextActions> <MenuItem Command="{Binding Path=BindingContext.Delete, Source={x:Reference Name=MyPageName}}" CommandParameter="{Binding .}" Text="{i18n:Translate Delete}" IsDestructive="True" /> </ViewCell.ContextActions>

    And give your Page a name (in my case MyPageName) with the x:Name attribute. Now you tell your MenuItem to get the Command from the BindingContext of your page

    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Wednesday, January 6, 2016 3:31 PM

All replies

  • User103165 posted

    This is a bit tricky.

    Because you use data-binding you are now looking for the Delete Command on the MyObject, while it is actually in your ViewModel. So you have to tell where it can find the Delete Command.

    Do it like this;

    <ViewCell.ContextActions> <MenuItem Command="{Binding Path=BindingContext.Delete, Source={x:Reference Name=MyPageName}}" CommandParameter="{Binding .}" Text="{i18n:Translate Delete}" IsDestructive="True" /> </ViewCell.ContextActions>

    And give your Page a name (in my case MyPageName) with the x:Name attribute. Now you tell your MenuItem to get the Command from the BindingContext of your page

    • Marked as answer by Anonymous Thursday, June 3, 2021 12:00 AM
    Wednesday, January 6, 2016 3:31 PM
  • User140108 posted

    Great answer, thank you very much!

    Wednesday, January 6, 2016 3:39 PM
  • User174822 posted

    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 ()));
    
    Monday, February 22, 2016 11:33 PM
  • User189777 posted

    @GeraldVersluis You really made my day!!

    Friday, March 11, 2016 4:01 PM
  • User203004 posted

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

    Wednesday, April 13, 2016 6:24 AM
  • User203004 posted

    @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??

    Wednesday, April 13, 2016 7:06 AM
  • User103165 posted

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

    Wednesday, April 13, 2016 7:07 AM
  • User203004 posted

    @GeraldVersluis : I have some confusion

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

                <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.

    Wednesday, April 13, 2016 7:16 AM
  • User103165 posted

    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.

    Wednesday, April 13, 2016 9:57 AM
  • User203004 posted

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

    Wednesday, April 20, 2016 8:42 AM
  • User103165 posted

    Glad to hear! Happy coding!

    Wednesday, April 20, 2016 8:42 AM
  • User193688 posted

    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" :(

    Friday, July 1, 2016 3:43 PM
  • User176749 posted

    @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?

    Tuesday, July 26, 2016 1:00 PM
  • User176749 posted

    @QuentinDujardin said: 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" :(

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

    Tuesday, July 26, 2016 1:01 PM
  • User176749 posted

    @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

    Tuesday, July 26, 2016 2:24 PM
  • User176749 posted

    @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);            
                    });
                }
            }
    
    Tuesday, July 26, 2016 4:25 PM
  • User176749 posted

    @QuentinDujardin said: 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" :(

    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>
    
    Tuesday, July 26, 2016 4:34 PM
  • User103165 posted

    Good to see you worked it out :smile:

    Tuesday, July 26, 2016 5:31 PM
  • User278172 posted

    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

    Sunday, November 27, 2016 8:25 PM
  • User2148 posted

    @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 ;)

    Thursday, January 12, 2017 9:12 AM
  • User103165 posted

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

    Thursday, January 12, 2017 9:18 AM
  • User2148 posted

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

    Thursday, January 12, 2017 9:22 AM
  • User103165 posted

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

    Haha no worries!

    Thursday, January 12, 2017 9:26 AM
  • User2148 posted

    @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"?

    Thursday, January 12, 2017 9:45 AM
  • User103165 posted

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

    Thursday, January 12, 2017 9:49 AM
  • User2148 posted

    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; }
    
        }
    }
    
    Thursday, January 12, 2017 9:52 AM
  • User2148 posted

    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;}
        }
    }
    
    Thursday, January 12, 2017 9:53 AM
  • User2148 posted

    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....)

    Thursday, January 12, 2017 9:54 AM
  • User2148 posted

    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

    Thursday, January 12, 2017 10:21 AM
  • User103165 posted

    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.

    Thursday, January 12, 2017 12:51 PM
  • User2148 posted

    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

    Thursday, January 12, 2017 12:53 PM
  • User292111 posted

    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

    Wednesday, February 8, 2017 11:28 AM
  • User248229 posted

    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); });

    Thursday, November 16, 2017 5:23 PM
  • User377566 posted

    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: xaml <MenuItem Text="Delete" IsDestructive="True" Command="{Binding DeleteCommand}" CommandParameter="{Binding .}"> <MenuItem.BindingContext> <vm:MyPageViewModel/> </MenuItem.BindingContext> </MenuItem>

    Friday, September 14, 2018 4:07 PM