Search code examples
xamlxamarinxamarin.formsmvvm

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


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


Solution

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