4

I want a collection of buttons that show their corresponding item's index in MyObservableCollection as Text, and execute a Command with the CommandParameter also being this index. How can I achieve this?

<StackLayout BindableLayout.ItemsSource="{Binding MyObservableCollection}">
  <BindableLayout.ItemTemplate>
    <DataTemplate>
      <Button Text="{Binding [index of the item in MyObservableCollection]}" 
              Command="{Binding MyCommand}"
              CommandParameter="{Binding [index of the item in MyObservableCollection]}" />
    </DataTemplate>
  </BindableLayout.ItemTemplate>
</StackLayout>
2
  • 1
    As far as I know, this is a tricky one. I think the most elegant solution might be to use a Converter where you can bind both the MyObservableCollection (have to do this through x:Referencing) and the item itself ({Binding .}). Then the converter can lookup the item in the collection and return it's index. However keep in mind that this will not react to ordering changes of the collection. Side note, haven't tried a converter with multiple parameters, but this should help: stackoverflow.com/questions/11323169/…
    – user10608418
    Commented Apr 18, 2019 at 9:49
  • @Knoop Since it doesn't react to ordering changes of the collection anyway I feel like it's much simpler to just add a parameter to the object that is set to the index in the loop where the object is added to the collection. It's an awful hack but it should serve the same purpose.
    – Zebrastian
    Commented Apr 18, 2019 at 10:13

1 Answer 1

3

So lets do the "hacky" thing you mentioned in the comments in the right "un-hacky" way^^

I would build something along the following lines. First we make an IIndexable interface:

public interface IIndexable
{
    int Index { get; set; }
}

We now make our own implementation of ObservableCollection<T> like this:

public class IndexableObservableCollection<T> : ObservableCollection<T> where T : IIndexable
{
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
            case NotifyCollectionChangedAction.Replace:
            case NotifyCollectionChangedAction.Move:
                for (int i = 0; i < e.NewItems.Count; i++)
                    if (e.NewItems[i] is IIndexable indexableItem)
                        indexableItem.Index = e.NewStartingIndex + i;
                break;
        }
        base.OnCollectionChanged(e);
    }
}

For now I've only done the most basic implementation in the switch here (granted in this case the switch can be replaced by an if statement, but this makes it easier for you to implement all the options), you'll have to look into the NotifyCollectionChangedEventArgs yourself and implement the cases accordingly.

After this just have classes you want to be able to show an index of implement the IIndexable interface and put them in an IndexableObservableCollection and this will automatically set their index when they're added. in xaml you can then just use {Binding Index} to bind to this automatically set index.

Hope this helps

4
  • I like this solution, but I'm having trouble binding to the Command that exists in my ViewModel. How do I set the BindingContext of the Command only?
    – Zebrastian
    Commented Apr 18, 2019 at 13:37
  • 1
    That is a different question, so out of scope. But you can look at an answer I've given here: stackoverflow.com/a/54709219/10608418. Basically you're using an x:Reference to bind to another BindingContext.
    – user10608418
    Commented Apr 18, 2019 at 13:44
  • @QuanDar I'm sorry to hear that, what's not working for you?
    – user10608418
    Commented Dec 17, 2020 at 14:49
  • @Knoop I think it's because I'm using nested lists. I've opened a new question here: stackoverflow.com/questions/65343752/… =]
    – QuanDar
    Commented Dec 17, 2020 at 15:40

Not the answer you're looking for? Browse other questions tagged or ask your own question.