26

Is there any way to get the index of the current ItemsControl item in WPF?

For example, I want to do something like:

<ItemsControl>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBox Text="{Binding current_index}">
            </TextBox>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

so that after this, the first TextBox will show text "0", second "1", third "2" ....

herohuyongtao
  • 45,575
  • 23
  • 118
  • 159
  • possible duplicate of [WPF ItemsControl the current ListItem Index in the ItemsSource](http://stackoverflow.com/questions/6511180/wpf-itemscontrol-the-current-listitem-index-in-the-itemssource) – har07 Mar 13 '14 at 13:48

5 Answers5

38

I would suggest looking at:

WPF ItemsControl the current ListItem Index in the ItemsSource

It explains how to work around the fact that there isn't a built in Index property on the ItemsControl.

EDIT:

I tried the following code:

<Window.Resources>
    <x:Array Type="{x:Type sys:String}" x:Key="MyArray">
        <sys:String>One</sys:String>
        <sys:String>Two</sys:String>
        <sys:String>Three</sys:String>
    </x:Array>
</Window.Resources>
<ItemsControl ItemsSource="{StaticResource MyArray}" AlternationCount="100" >
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Path=(ItemsControl.AlternationIndex), 
                RelativeSource={RelativeSource TemplatedParent}, 
                StringFormat={}Index is {0}}">
            </TextBlock>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl >

And get a window with three TextBlocks like:

[Index is 0]
[Index is 1]
[Index is 2]
Community
  • 1
  • 1
FishySwede
  • 1,523
  • 16
  • 22
  • 2
    For others who find this, I like and used Saykor's solution. I heard that you cannot rely on AlternationCount always starting at zero. Feels like a hack. Rather use a converter and pass it the info needed to determine index. – Skychan May 19 '16 at 15:49
  • 1
    @Skychan Can you provide further arguments, why AlternationCount used this way should be unreliable? – Leonidas Jul 26 '17 at 08:52
  • 2
    @Leonidas: I found that the AlternationIndex value was often passed through to my code-behind as zero for all the items while layouts are being calculated, so it didn't work well for me as I wanted it for more than just display-time bindings. Using ReferenceEquals against items in ItemsSource to determine their index was a reliable alternative. – Jason Williams Oct 18 '18 at 22:28
  • This method is not reliable at all, and if you have Virtualization enabled it gets even worse... maybe it can work for very simple list and in that case you can bind the AlternationCount to the count of items in the list and maybe set the Text binding to be OneTime and create AddOneConverter to start from 1 instead of 0 – Mr. Noob Dec 01 '20 at 16:24
  • Possible reference for above commented AlternationIndex unreliability: https://stackoverflow.com/a/17962582/7224691 – Ben Dec 17 '20 at 04:53
7

Check this out

 <ItemsControl ItemsSource="{Binding Items}" Name="lista">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Vertical">
                    <TextBlock>
                        <TextBlock.Text>
                            <MultiBinding Converter="{StaticResource converter}">
                                <Binding Path="."/>
                                <Binding ElementName="lista" Path="ItemsSource"/>
                            </MultiBinding>
                        </TextBlock.Text>
                    </TextBlock>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

Converter looks like this

 public class conv : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        ObservableCollection<string> lista = (ObservableCollection<string>)values[1];
        return String.Concat(lista.IndexOf(values[0].ToString()), " ", values[0].ToString());
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

As a result enter image description here

Maximus
  • 3,273
  • 3
  • 12
  • 26
  • 1
    Note that this works only if the view matches exactly the original source, i.e. is not for example sorted or filtered. – Peter Duniho Jan 22 '15 at 23:06
6

Here how I get ItemIndex

<ItemsControl>
        <ItemsControl.Resources>
            <CollectionViewSource x:Key="ProductItems" Source="{Binding SelectedScanViewModel.Products}">
                <CollectionViewSource.SortDescriptions>
                    <componentModel:SortDescription PropertyName="ProductName" Direction="Ascending"/>
                </CollectionViewSource.SortDescriptions>
            </CollectionViewSource>
        </ItemsControl.Resources>
        <ItemsControl.ItemsSource>
            <Binding Source="{StaticResource ProductItems}"/>
        </ItemsControl.ItemsSource>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel HorizontalAlignment="Center">
                    <TextBlock Text="{Binding ProductName}" HorizontalAlignment="Center" />
                    <TextBox Name="txtFocus" Text="{Binding Qty}" MinWidth="80" HorizontalAlignment="Center"
                                     behaviors:SelectTextOnFocus.Active="True">
                        <TextBox.TabIndex>
                            <MultiBinding Converter="{StaticResource GetIndexMultiConverter}" ConverterParameter="0">
                                <Binding Path="."/>
                                <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}" Path="ItemsSource"/>
                            </MultiBinding>
                        </TextBox.TabIndex>
                    </TextBox>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid Columns="{Binding SelectedScanViewModel.Products.Count}"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>

And the converter:

public class GetIndexMultiConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var collection = (ListCollectionView)values[1];
        var itemIndex = collection.IndexOf(values[0]);

        return itemIndex;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException("GetIndexMultiConverter_ConvertBack");
    }
}

By this way you can bind every type of collection to the ItemSource and he will be change to ListCollectionView. So the converter will work for different collection type.

xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
Saykor
  • 657
  • 12
  • 13
  • Here is a similar alternative solution that uses the ItemContainerGenerator to determine index. http://stackoverflow.com/questions/7290147/is-possible-to-get-a-index-from-a-item-in-a-list – Skychan May 19 '16 at 16:10
  • I think this approach won't work correctly: IndexOf uses Equals and if the source collection has duplicates (e.g. two same string values) IndexOf will always return first item. – aderesh Jan 03 '17 at 07:02
  • I think a solution that uses ItemContainerGenerator is better. Thanks. – aderesh Jan 03 '17 at 07:03
  • @aderesh surely you can just change the Convert method to do whatever you like (e.g. use ReferenceEquals instead)? – Ruben9922 Feb 13 '20 at 09:11
  • Note that if you're using the converter somewhere that expects a string (e.g. ``) you will need to return a string from the `Convert` method, either with `return itemIndex.ToString()` or (more generally) `return System.Convert.ChangeType(itemIndex, targetType)`. – Ruben9922 Feb 13 '20 at 11:08
1

If your goal is to have a button in the ItemTemplate work properly, I would use the DataContext. You should also be able to find the index from the DataContext and ItemsSource using LINQ.

If using commands

Command="{Binding DataContext.TestCmd, ElementName=Parent_UC}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Mode=Self}}"

If using events, use the sender.

private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
   if(sender is Button b)
   {
      if(b.DataContext is ClassType t)
      { enter code here }
   }
}
CBFT
  • 179
  • 8
0

I did it via the converter that calculate the index of added element.

It works one way only. If you delete items somehow or collection changing you shoud to use thomething else. And you shoud to create separate converter for every collection which elements you need to be indexed.

public class LineMultiplierConverter : IValueConverter
{
    private int m_lineIndex = 0;
    Line m_curentLine = null;

    /// <summary>
    /// Base value that will be multiplied
    /// </summary>
    public double BaseValue { get; set; }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var line = value as Line;

        if (line == null)
            return BaseValue;

        bool newLine = line != m_curentLine; //check the reference because this method will called twice on one element by my binding

        if (newLine)
        {
            m_lineIndex++;
            m_curentLine = line; 
        }

        return BaseValue * m_lineIndex;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

I use it in xaml this way

<UserControl.Resources>
    <sys:Double x:Key="BusinessRowHeight">22</sys:Double>
    <local:LineMultiplierConverter x:Key="LineXConverter" BaseValue="{StaticResource BusinessRowHeight}" />
</UserControl.Resources>
<ItemsControl Grid.Row="1" ItemsSource="{Binding CarBusiness}" Margin="0 5 0 0">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Line StrokeThickness="1" Stroke="LightGray"  
                    X1="0" 
                    Y1="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource LineXConverter}}" 
                    X2="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl, Mode=FindAncestor}, Path=ActualWidth}" 
                    Y2="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource LineXConverter}}"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

This draws for me a lines for every element in collection with BaseValue offset for X coordinate.

Kamerton
  • 135
  • 1
  • 7