Home > Back-end >  In a ListView how to send the clicked object back to the command in the view model - Xamarin Forms
In a ListView how to send the clicked object back to the command in the view model - Xamarin Forms

Time:05-23

Given the following ListView, I'd like to have a command that would send the clicked object, in this case the Address, back to a command in the view model - SelectNewAddress or DeleteAddress.

            <StackLayout VerticalOptions="FillAndExpand" Padding="10,15,10,15">
                <Label Text="Addresses" FontSize="22" HorizontalTextAlignment="Center" FontAttributes="Bold" Padding="0,0,0,7" TextColor="#404040" />
                <StackLayout VerticalOptions="FillAndExpand">
                    <flv:FlowListView FlowColumnCount="1"
                                        HeightRequest="200"
                                        SeparatorVisibility="None"
                                        HasUnevenRows="True"
                                        FlowItemsSource="{Binding AllAddresses}">
                        <flv:FlowListView.FlowColumnTemplate>
                            <DataTemplate x:DataType="popups:AddressItem">
                                <Grid ColumnDefinitions="*,35" Padding="0,0,0,15" x:Name="Item">
                                    <Grid Grid.Column="0">
                                        <Grid.GestureRecognizers>
                                            <TapGestureRecognizer 
                                                Command="{Binding SelectNewAddress}" />
                                        </Grid.GestureRecognizers>
                                        <Label Text="{Binding MainAddress}" 
                                                LineBreakMode="TailTruncation" 
                                                HorizontalTextAlignment="Start" 
                                                VerticalTextAlignment="Center"
                                                FontSize="18"
                                                TextColor="{StaticResource CommonBlack}"/>
                                    </Grid>
                                    <Grid Grid.Column="1" IsVisible="{Binding IsSelected}"  >
                                        <Grid.GestureRecognizers>
                                            <TapGestureRecognizer 
                                                Command="{Binding SelectNewAddress}"/>
                                        </Grid.GestureRecognizers>
                                        <StackLayout Padding="10,0,0,0">
                                            <flex:FlexButton Icon="check.png" 
                                                                WidthRequest="25" 
                                                                HeightRequest="25"
                                                                CornerRadius="18"
                                                                BackgroundColor="{StaticResource Primary}" 
                                                                ForegroundColor="{StaticResource CommonWhite}" 
                                                                HighlightBackgroundColor="{StaticResource PrimaryDark}"
                                                                HighlightForegroundColor="{StaticResource CommonWhite}"/>
                                        </StackLayout>
                                    </Grid>
                                    <Grid Grid.Column="1" IsVisible="{Binding IsSelected, Converter={StaticResource invertBoolConverter}}">
                                        <Grid.GestureRecognizers>
                                            <TapGestureRecognizer Command="{Binding DeleteAddress} />
                                        </Grid.GestureRecognizers>
                                        <StackLayout Padding="10,0,0,0">
                                            <flex:FlexButton Icon="deleteCard.png" 
                                                                WidthRequest="25" 
                                                                HeightRequest="25"
                                                                CornerRadius="18"
                                                                BackgroundColor="{StaticResource WooopDarkGray}" 
                                                                ForegroundColor="{StaticResource CommonWhite}" 
                                                                HighlightBackgroundColor="{StaticResource PrimaryDark}"
                                                                HighlightForegroundColor="{StaticResource CommonWhite}"/>
                                        </StackLayout>
                                    </Grid>
                                </Grid>
                            </DataTemplate>
                        </flv:FlowListView.FlowColumnTemplate>
                    </flv:FlowListView>
                </StackLayout>

The commands in the view model are the following:

        ...

        public ICommand SelectNewAddress { get; set; }
        public ICommand DeleteAddress { get; set; }

        ...

        public AddressSelectionViewModel()
        {
            DeleteAddress = new Command(DeleteAddressCommand);
            SelectNewAddress = new Command(SelectNewAddressCommand);
        }
        
        ...
        private void SelectNewAddressCommand(object obj)
        {
            try
            {
                var item = (AddressItem)obj;
                AddressHelper.UpdateAddress(item.DeliveryAddressLocation);
                UpdateAddresses();
            }
            catch (Exception ex)
            {
                // TODO
            }
        }

        private void DeleteAddressCommand(object obj)
        {
            try
            {
                var item = (AddressItem)obj;
                AddressHelper.RemoveAddress(item.DeliveryAddressLocation);
                UpdateAddresses();
            }
            catch (Exception ex)
            {
                // TODO
            }
        }

I want the object obj passed to SelectNewAddressCommand and DeleteAddressCommand to be the address clicked on the ListView

CodePudding user response:

First make sure you have included your view model as DataType and view as Class inside the ContentPage:

xmlns:pages="clr-namespace:your.namespace.ViewModels"
x:DataType="pages:AddressSelectionViewModel"
x:Class="your.namespace.Views.AddressSelectionPage"
<ContentPage xmlns="..."
             xmlns:x="..." 
             xmlns:flv="..." 
             xmlns:popups="..." 
             xmlns:flex="..." 
             xmlns:views="..." 
             xmlns:xct="..." 
             xmlns:pages="clr-namespace:your.namespace.ViewModels"
             x:DataType="pages:AddressSelectionViewModel"
             x:Class="your.namespace.Views.AddressSelectionPage"
             Shell.FlyoutItemIsVisible="..."
             Shell.NavBarIsVisible="..."
             Shell.TabBarIsVisible="...">

Inside the top Grid element add property x:Name="Item" ("Item" is only used as an example, you can name it anything):

                    <flv:FlowListView FlowColumnCount="1"
                                        HeightRequest="200"
                                        SeparatorVisibility="None"
                                        HasUnevenRows="True"
                                        FlowItemsSource="{Binding AllAddresses}">
                        <flv:FlowListView.FlowColumnTemplate>
                            <DataTemplate x:DataType="popups:AddressItem">
                                <Grid ColumnDefinitions="*,35" Padding="0,0,0,15" x:Name="Item"> <!-- Here -->

Then we change the Command and CommandParameter of the TapGestureRecognizer to the following:

<TapGestureRecognizer 
        Command="{Binding Path=SelectNewAddress, Source={RelativeSource AncestorType={x:Type pages:AddressSelectionViewModel}}}" 
        CommandParameter="{Binding Source={x:Reference Item}, Path=BindingContext}" />
<TapGestureRecognizer 
        Command="{Binding Path=DeleteAddress, Source={RelativeSource AncestorType={x:Type pages:AddressSelectionViewModel}}}" 
        CommandParameter="{Binding Source={x:Reference Item}, Path=BindingContext}" />

In the Command we specify the function as Path, then we clarify that the source of this function is inside the view model through AncestoryType. When inside a list view we cannot reference properties outside the object being iterated. Hence, we need to specify the desired source.

So now we are referencing the actual function. But we aren't sending the object obj as a parameter yet.

In the CommandParameter we have to pass the currently bound object with Path and Source. Note that in Source we are referencing has the name Item we defined as the x:Name of the Grid earlier.

CodePudding user response:

  1. Make sure the page has the viewmodel as its BindingContext. (If you are doing mvvm, you've already done this.)

  2. Give <flv:FlowListView a name:

<flv:FlowListView x:Name="theListView" ... >
  1. The item needs to refer to the command in the page's viewmodel. BindingContext propagates down through the hierarchy, so this is easily done relative to the listview name:
    <flv:FlowListView.FlowColumnTemplate>
        <DataTemplate>
            ...
         <TapGestureRecognizer 
              Command="{Binding BindingContext.SelectNewAddress, Source={x:Reference theListView}}" ...
  1. The item's BindingContext is the item model, so that is easily passed as a parameter:
         <TapGestureRecognizer 
              Command=... CommandParameter="{Binding .}" />

NOTE: Differences between this and Wizard's answer:

  • {Binding .} is all that is needed to refer to the item itself.
  • Instead of using RelativeSource, which requires specifying a Type, my personal preference is to name a view, then refer to that name. I find this easier to read and to remember how to do.
  • I left out all details that are not relevant to the question. The above steps are sufficient. (x:DataType commands are good for performance, so I am in no way suggesting not to do them. But that is a separate topic, IMHO.)
  • Related