Forum Xamarin.Forms

Announcement:

The Xamarin Forums have officially moved to the new Microsoft Q&A experience. Microsoft Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

To create new threads and ask questions head over to Microsoft Q&A for .NET and get involved today.

Struggling with Tag gesture and Command Parameters

TBelvinTBelvin Member ✭✭
edited November 2019 in Xamarin.Forms

The following is a snippet from my XAML:

            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout>
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="0.7*" />
                                    <ColumnDefinition Width="0.3*" />
                                </Grid.ColumnDefinitions>
                                <Label Text="{Binding AddressTypeName}" Grid.Column="0"/>
                                <Label Text="&#xE74D;"
                                       Grid.Column="1"
                                       FontFamily="{StaticResource SegoeMDL2Assets}" FontSize="Medium" HorizontalOptions="End">
                                    <Label.GestureRecognizers>
                                        <TapGestureRecognizer Command="{Binding DeleteCommand}" CommandParameter="{Binding .}"/>
                                    </Label.GestureRecognizers>
                                </Label>

                            </Grid>
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>

            </ListView.ItemTemplate>

Basically, I am adding a trash can icon on every row and Binding the method DeleteCommand passing the parameter of the object on that row (which is actually a class for AddressType.

In my ViewModel, the following is where I wire up the commands. Snippet:

        void DeleteCommandExecute(AddressType address)
       {
        if (IsBusy)
            return;
        IsBusy = true;

        try
        {
            DataStore.DeleteAddressTypeAsync(address.Id);
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex);
        }
        finally
        {
            IsBusy = false;
        }
    }

In my codebehind file, I set the BindingContext to the viewmodel

    private AddressTypeViewModel viewModel;
    public AddressTypes()
    {
        InitializeComponent();
        BindingContext = viewModel = new AddressTypeViewModel();
        stack = Addresspopup.PopupView.ContentTemplate.CreateContent() as StackLayout;

    }

The DeleteCommand never executes. It appears no binding occured (even though the {Binding AddressTypeName} works as expected.

I have no idea why.

I also tried just catching the Tap event in my code behind by doing this in my XAML:

                                <Label Text="&#xE74D;"
                                       Grid.Column="1"
                                       FontFamily="{StaticResource SegoeMDL2Assets}" FontSize="Medium" HorizontalOptions="End">
                                    <Label.GestureRecognizers>
                                        <TapGestureRecognizer Tapped="OnDelete_Tapped" CommandParameter="{Binding .}"/>
                                    </Label.GestureRecognizers>
                                </Label>

I added this to my code behind:

    public void OnDelete_Tapped(object sender, EventArgs e)
    {
        viewModel.DeleteCommand.Execute(e);  //deletes the item

        viewModel.LoadAddressTypesCommand.Execute(true); //reload the addresstypes
    }

The DeleteCommand does NOT execute! I put a breakpoint in that method in my view model - nothing happens - never hits it.

Can someone shed any light on this? Thanks in advance!

Answers

  • TBelvinTBelvin Member ✭✭
    edited November 2019

    Arrg - let me repost the code parts:

    In the binding to command in view model version, the xaml code looks like this:

                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <StackLayout>
                                <Grid>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto"/>
                                    </Grid.RowDefinitions>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="0.7*" />
                                        <ColumnDefinition Width="0.3*" />
                                    </Grid.ColumnDefinitions>
                                    <Label Text="{Binding AddressTypeName}" Grid.Column="0"/>
                                    <Label Text="&#xE74D;"
                                           Grid.Column="1"
                                           FontFamily="{StaticResource SegoeMDL2Assets}" FontSize="Medium" 
                                           HorizontalOptions="End">
                                        <Label.GestureRecognizers>
                                            <TapGestureRecognizer Command="DeleteCommand" CommandParameter="{Binding .}"/>
                                        </Label.GestureRecognizers>
                                    </Label>
    
                                </Grid>
                            </StackLayout>
                        </ViewCell>
                    </DataTemplate>
    
                </ListView.ItemTemplate>
    

    The viewmodel code is:

        public ICommand DeleteCommand => new Command<AddressType>(DeleteCommandExecute);
    
        void DeleteCommandExecute(AddressType address)
        {
            if (IsBusy)
                return;
            IsBusy = true;
    
            try
            {
                DataStore.DeleteAddressTypeAsync(address.Id);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
            finally
            {
                IsBusy = false;
            }
        }``
    

    In the case of using just the code behind and just handle the tapped event, the xaml looks like this:

      <TapGestureRecognizer Tapped="OnDelete_Tapped" CommandParameter="{Binding .}"/>
    

    The code behind looks like this:

       public void OnDelete_Tapped(object sender, EventArgs e)
        {
            viewModel.DeleteCommand.Execute(e);
    
            viewModel.LoadAddressTypesCommand.Execute(true);
        }`
    

    The DeleteCommand does NOT execute but the LoadAddressTypesCommand does.

    Thoughts?

  • LeonLuLeonLu Member, Xamarin Team Xamurai

    @TBelvin Do you want to achieve the result like following GIF?

    If so, your data binding code of command is wrong. you should change it like following format.

          <Label.GestureRecognizers>
               <TapGestureRecognizer  Command="{Binding Path=BindingContext.DeleteCommand ,Source={x:Reference mysl}}" CommandParameter="{Binding .}" NumberOfTapsRequired="1"/>
          </Label.GestureRecognizers>
    

    You are binding to the Command that is in your ViewModel, not on the actual it on the list. You can add the command to your Model or do a reference binding to the page:

    give a x:Name="mysl" in your content page and then do: my all xaml code.

    <StackLayout x:Name="mysl">
        <ListView x:Name="mylistview" ItemsSource="{Binding persons}" HasUnevenRows="True">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell Height="100">
    
                        <StackLayout>
    
                            <Grid>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="Auto"/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="0.7*" />
                                    <ColumnDefinition Width="0.3*" />
                                </Grid.ColumnDefinitions>
    
                                <Label Text="{Binding Name}" Grid.Column="0"  BackgroundColor="AliceBlue"/>
                                <Label Text="&#xE74D;"
                                       Grid.Column="1"
                                        FontSize="Medium" 
                                      BackgroundColor="Red"
                                       HorizontalOptions="End">
                                    <Label.GestureRecognizers>
                                        <TapGestureRecognizer  Command="{Binding Path=BindingContext.DeleteCommand ,Source={x:Reference mysl}}" CommandParameter="{Binding .}" NumberOfTapsRequired="1"/>
                                    </Label.GestureRecognizers>
                                </Label>
    
                            </Grid>
                        </StackLayout>
    
    
                    </ViewCell>
                </DataTemplate>
    
            </ListView.ItemTemplate>
        </ListView>
    </StackLayout>
    

    My backend code.

       public MainPage()
        {
           InitializeComponent();
           mysl.BindingContext= new PersonsViewModel(Navigation); 
         }
    

    My PersonsViewModel

        public class PersonsViewModel
       {
        public ObservableCollection<Person> persons { get; set; }
        public ICommand DeleteCommand { protected set; get; }
    
        public PersonsViewModel(INavigation navigation)
        {
    
            persons = new ObservableCollection<Person>();
            persons.Add(new Person() { Name=  "Leon1" ,Image= "Clover.png" });
            persons.Add(new Person() { Name = "Leon2", Image = "Clover.png" });
            persons.Add(new Person() { Name = "Leon3", Image = "Clover.png" });
            persons.Add(new Person() { Name = "Leon4", Image = "Clover.png" });
            persons.Add(new Person() { Name = "Leon5", Image = "Clover.png" });
            DeleteCommand = new Command<Person>((key) =>
            {
                persons.Remove(key);
             });
    
          }
      }
    

    Here is my demo.

  • TBelvinTBelvin Member ✭✭

    Thanks. Your demo did work exactly the way I want my code to work. I should have provided my entire XAML. I don't really understand the Path and Source properties to the Binding command.

    Below is my entire page. Note that I am using SyncFusion's popup as well.

    `<?xml version="1.0" encoding="utf-8" ?>

       <popuplayout:SfPopupLayout x:Name="Addresspopup" Closing="Popup_OnClosing" Opened="Popup_OnOpen">
        <popuplayout:SfPopupLayout.PopupView>
            <popuplayout:PopupView AnimationMode="SlideOnTop" AcceptButtonText="Submit">
                <popuplayout:PopupView.ContentTemplate>
                    <DataTemplate x:Name="typesPopupTemplate">
                        <StackLayout>
                            <border:SfBorder HorizontalOptions="FillAndExpand" HasShadow="True" ShadowColor="Gray"
                                             BorderColor="LightGray" Margin="5,5,5,0" BorderWidth="1"
                                             BackgroundColor="White"
                                             CornerRadius="9">
                                <Entry x:Name="NewType" Text="{Binding NewTypeName}"/>
                            </border:SfBorder>
                        </StackLayout>
                    </DataTemplate>
                </popuplayout:PopupView.ContentTemplate>
            </popuplayout:PopupView>
        </popuplayout:SfPopupLayout.PopupView>
        <popuplayout:SfPopupLayout.Content>
            <ListView x:Name="AddressTypeView" ItemsSource="{Binding AddressTypes}" SeparatorVisibility="None">
                <ListView.Header>
                    <StackLayout>
                        <Label Text="Types of Addresses" FontSize="Medium" FontAttributes="Bold" HorizontalTextAlignment="Center" Padding="10"/>
                    </StackLayout>
                </ListView.Header>
                <ListView.Footer>
                    <StackLayout>
                        <Label Text="Add New" BackgroundColor="#f0f0f0" TextColor="#006bcd" VerticalTextAlignment="Center"
                                       VerticalOptions="Center" 
                                       HorizontalTextAlignment="Center" FontSize="20">
                            <Label.GestureRecognizers>
                                <TapGestureRecognizer Tapped="OnAddNew_Tapped" />
    
                            </Label.GestureRecognizers>
                        </Label>
                    </StackLayout>
                </ListView.Footer>
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <StackLayout x:Name="vcSL">
                                <Grid>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto"/>
                                    </Grid.RowDefinitions>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="0.7*" />
                                        <ColumnDefinition Width="0.3*" />
                                    </Grid.ColumnDefinitions>
                                    <Label Text="{Binding AddressTypeName}" Grid.Column="0"/>
                                    <Button FontFamily="{StaticResource SegoeMDL2Assets}" HorizontalOptions="End"
                                            Text="&#xE74D;" Grid.Column="1" Command="{Binding Path=BindingContext.DeleteCommandX, Source={x:Reference vcSL}}" CommandParameter="{Binding .}"/>
    
                                </Grid>
                            </StackLayout>
                        </ViewCell>
                    </DataTemplate>
    
                </ListView.ItemTemplate>
    
            </ListView>
        </popuplayout:SfPopupLayout.Content>
    </popuplayout:SfPopupLayout>
    

    `

    I am confused on how to bind the button (or label with a tap gesture) to my ViewModel. I set the bindingcontext to the view model in the code behind. If execute the code I have posted, the DeleteCommandX never gets executed (I believe because the source is a StackLayout that is contained in a DataTemplate - but don't know. Thanks again for your help.

  • TobyKTobyK GBMember ✭✭✭
    edited November 2019

    Hi @TBelvin The binding in ListView DataTemplate binds to the list items themselves by default. Using the Path=BindingContext etc allows you to binding it to a named instance instead - using {x:Reference NameOfViewPage} for example, which your ViewModel is bound to and contains your Items that you bind to the ListView. I hope that makes sense.

    So I think you are just missing the x:Name="vcSL" in your view definition (at the top with the xmls parts) - so it can link it to the view that the binding context is set to your viewmodel.

Sign In or Register to comment.