问题
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
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);
}
回答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
- 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.
回答2:
Since it seems very hard to get an Index inside the XAML I created a different solution. I still hope someone knows how to get Index inside nested stacklayout lists. My teacher says it is bad practice to loop over things when not needed for performance.
<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.DeleteSpecificationDetailEntryCommand, Source={x:Reference Page}}"
CommandParameter="{Binding .}"/>
</Grid>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
<!-- Add specification detail -->
<Button Text="Voeg specificatie toe"
Command="{Binding Path=BindingContext.AddSpecicifationDetailEntryCommand, Source={x:Reference Page}}"
CommandParameter="{Binding .}"
HorizontalOptions="Start" />
<Button Text="{Binding Title, StringFormat='Verwijder {0}'}"
Style="{StaticResource cancel-button-style}"
FontSize="12"
Command="{Binding Path=BindingContext.DeleteSpecificationGroupEntryCommand, Source={x:Reference Page}}"
CommandParameter="{Binding .}"
HorizontalOptions="Start" />
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
<!-- Add Specification group -->
<Button Text="Voeg nieuwe specificatie toe"
Command="{Binding AddSpecicifationGroupEntriesCommand}" />
Methods for adding and removing specs
private void ExecuteAddSpecicifationGroupEntriesCommand()
{
Product.Specifications.Add(new SpecificationDbViewModel()
{
SpecificationDetails = new ObservableCollection<SpecificationDetailDbViewModel>()
{
new SpecificationDetailDbViewModel()
}
});
}
private void ExecuteDeleteSpecicifationGroupEntriesCommand(SpecificationDbViewModel specsViewModel)
{
Product.Specifications.Remove(specsViewModel);
}
private void ExecuteAddSpecicifationDetailEntryCommand(SpecificationDbViewModel specsViewModel)
{
Product.Specifications[Product.Specifications.IndexOf(specsViewModel)].SpecificationDetails.Add(new SpecificationDetailDbViewModel());
}
private void ExecuteDeleteSpecificationDetailEntryCommand(SpecificationDetailDbViewModel specsDetailViewModel)
{
for(int i = 0; i < Product.Specifications.Count; i++)
{
if(Product.Specifications[i].SpecificationDetails.IndexOf(specsDetailViewModel) != -1)
{
Product.Specifications[i].SpecificationDetails.Remove(specsDetailViewModel);
return;
}
}
}
Create the commands inside the constructor of your viewmodel
AddSpecicifationDetailEntryCommand = new Command<SpecificationDbViewModel>((SpecificationDbViewModel specificationDbViewModel) => ExecuteAddSpecicifationDetailEntryCommand(specificationDbViewModel));
DeleteSpecificationDetailEntryCommand = new Command<SpecificationDetailDbViewModel>((SpecificationDetailDbViewModel specsDetailViewModel) => ExecuteDeleteSpecificationDetailEntryCommand(specsDetailViewModel));
AddSpecicifationGroupEntriesCommand = new Command(() => ExecuteAddSpecicifationGroupEntriesCommand());
DeleteSpecificationGroupEntryCommand = new Command<SpecificationDbViewModel>((SpecificationDbViewModel specificationDbViewModel) => ExecuteDeleteSpecicifationGroupEntriesCommand(specificationDbViewModel));
Properties
public ICommand AddSpecicifationDetailEntryCommand { get; }
public ICommand DeleteSpecificationDetailEntryCommand { get; }
public ICommand AddSpecicifationGroupEntriesCommand { get; }
public ICommand DeleteSpecificationGroupEntryCommand { get; }
来源:https://stackoverflow.com/questions/65343752/how-to-bind-index-of-item-in-nested-bindablelayout-itemssource-to-commandparamet