Search code examples
xamarin.formsitemssourcestacklayout

How to bind index of item in nested BindableLayout.ItemsSource to CommandParameter - Xamarin


I'm trying to figure out how you can get the index from a list inside XAML.

Context

A product has multiple specification categories/groups which contain the specification details.

  • Product & Ingrediënten are the specification groups.
  • Land van afkomst : Nederland are the specs details

enter image description here

In the XAML code, I'm using a nested list. The application needs to pass the Index so the users can delete and add specifications correctly.

The index at Binding Source="0" /> & CommandParameter="0" needs to be passed instead of "0".

<StackLayout BindableLayout.ItemsSource="{Binding Product.Specifications}">
    <BindableLayout.ItemTemplate>
        <DataTemplate>
            <StackLayout>
                <Entry Text="{Binding Title, Mode=TwoWay}" />
                <StackLayout BindableLayout.ItemsSource="{Binding SpecificationDetails}">
                    <BindableLayout.ItemTemplate>
                        <DataTemplate>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width=".44*" />
                                    <ColumnDefinition Width=".44*" />
                                    <ColumnDefinition Width=".12*" />
                                </Grid.ColumnDefinitions>
                                <Entry Grid.Row="0"
                                       Grid.Column="0"
                                       Text="{Binding Title}"
                                       Style="{StaticResource spec-entry-style}" />
                                <Entry Grid.Row="0"
                                       Grid.Column="1"
                                       Text="{Binding Description}"
                                       Style="{StaticResource spec-entry-style}" />
                                <!-- Delete specification detail -->
                                <Button Grid.Column="2"
                                        Text="X"
                                        Style="{StaticResource cancel-button-style}"
                                        Command="{Binding Path=BindingContext.DeleteSpecificationEntryCommand, Source={x:Reference Page}}">
                                    <Button.CommandParameter>
                                        <MultiBinding Converter="{StaticResource SpecsConverter}">
                                            <Binding Source="0" />
                                            <Binding Path="." />
                                        </MultiBinding>
                                    </Button.CommandParameter>
                                </Button>
                            </Grid>
                        </DataTemplate>
                    </BindableLayout.ItemTemplate>
                </StackLayout>
                <!-- Add specification detail -->
                <Button Text="Voeg specificatie toe"
                        Command="{Binding AddSpecicifationEntriesCommand}"
                        CommandParameter="0"
                        HorizontalOptions="Start" />
            </StackLayout>
        </DataTemplate>
    </BindableLayout.ItemTemplate>
</StackLayout>

<!-- Add Specification group -->
<Button Text="Voeg nieuwe specificatie toe"
        Command="{Binding AddNewSpecificationGroupCommand}" />

The specs group model:

public class SpecificationDbViewModel : INotifyPropertyChanged
{
    private int _id;
    private string _title;
    private ObservableCollection<SpecificationDetailDbViewModel> _specificationDetails;

    public int Id
    {
        get => _id;
        set 
        { 
            _id = value;
            RaisePropertyChanged(nameof(Id));
        }
    }

    public string Title
    {
        get => _title;
        set
        {
            _title = value;
            RaisePropertyChanged(nameof(Title));
        }
    }

    public ObservableCollection<SpecificationDetailDbViewModel> SpecificationDetails
    {
        get => _specificationDetails;
        set
        {
            _specificationDetails = value;
            RaisePropertyChanged(nameof(Title));
        }
    }


    public event PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

The specs detail model:

public class SpecificationDetailDbViewModel : INotifyPropertyChanged
{
    private int _id;
    private string _title;
    private string _description;

    public int Id
    {
        get => _id;
        set
        {
            _id = value;
            RaisePropertyChanged(nameof(Id));
        }
    }

    public string Title
    {
        get => _title;
        set
        {
            _title = value;
            RaisePropertyChanged(nameof(Title));
        }
    }
    public string Description
    {
        get => _description;
        set
        {
            _description = value;
            RaisePropertyChanged(nameof(Description));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

I'm using a MultiBinder converter to pass multiple values to the command. 1 method inside the ViewModel that removes the specifications:

private void ExecuteDeleteSpecificationEntryCommand(SpecificationDetailWithIndex specificationDetailWithIndex)
{
    Product.Specifications[specificationDetailWithIndex.Index].SpecificationDetails.Remove(specificationDetailWithIndex.SpecificationDetailDbViewModel);
}

Solution

    1. Don't bother your index, just can send THE object back as a parameter of the command.
    Command="{...}"  //same binding
    CommandParameter="{Binding .}"  //new line
    
    1. And define your command with the correct parameter in your ViewModel.
        public ICommand<SpecificationDetailDbViewModel> DeleteSpecificationEntryCommand => new Command<SpecificationDetailDbViewModel>(ExecuteDeleteSpecificationEntryCommand);
    
        private void ExecuteDeleteSpecificationDetailEntryCommand(SpecificationDetailDbViewModel item)
        {
            //remvoe item from collection
            Product.Specifications?.Remove(item);
        }
    

    And you can also use groups in the list view btw.