18

Is it possible to know the current item's Index in a ItemsControl?

EDIT This works!

<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>             <StackPanel Margin="10"> <!-- one -->                <TextBlock Text="{Binding Path=.,                      StringFormat={}Value is {0}}" /> <!-- two -->                 <TextBlock Text="{Binding Path=(ItemsControl.AlternationIndex),                      RelativeSource={RelativeSource TemplatedParent},                      FallbackValue=FAIL,                      StringFormat={}Index is {0}}" /> <!-- three -->                 <TextBlock Text="{Binding Path=Items.Count,                      RelativeSource={RelativeSource FindAncestor,                          AncestorType={x:Type ItemsControl}},                      StringFormat={}Total is {0}}" />             </StackPanel>         </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> 

It looks like this:

enter image description here

2
  • One way would be to create a custom IValueConverter and pass the items property as a parameter, then get the index by looking at the collection, though this is not very efficient. Commented Jun 28, 2011 at 18:49
  • That is not reliable either. What if it is filtered or sorted by a CollectionViewSource? Clever, I admit, but there are better ways. Commented Jun 28, 2011 at 22:01

5 Answers 5

26

I asked the same thing a while ago here

There isn't a built in Index property, but you can set the AlternationCount of your ItemsControl to something higher than your item count, and bind to the AlternationIndex

<TextBlock Text="{Binding Path=(ItemsControl.AlternationIndex), RelativeSource={RelativeSource Mode=TemplatedParent}, FallbackValue=FAIL, StringFormat={}Index is {0}}" /> 

It should be noted that this solution may not work if your ListBox uses Virtualization as bradgonesurfing pointed out here.

Sign up to request clarification or add additional context in comments.

5 Comments

Cheers. Just remember to set your AlternationCount higher than the expected count of items in the ItemsControl!
@Rachel: Would you know the solution if someone wanted 1 based index, instead of a zero based index? I guess we could add a converter to increment the count, but just curious if you are aware of a better way.
I wouldn't do this. See my experimentation results for the errors that occur. stackoverflow.com/questions/6511180/…
Hi Rachel, FYI BradDotNet worked out a different solution that doesn't use AlternationIndex. It's here, if you're curious.
Thanks @LynnCrumbling, good to know of alternate solutions :)
11

This is not quite an answer but a suggestion. Do not use the AlternationIndex technique as suggested. It seems to work first off but there are wierd side effects. It seems that you cannot guarantee that the AlternationIndex starts at 0.

On first rendering it works correctly

enter image description here

but re-sizing the Grid and then expanding results in the index not starting at zero any more. You can see the effect in the below image

enter image description here

This was generated from the following XAML. There are some custom components in there but you will get the idea.

<DataGrid VirtualizingPanel.VirtualizationMode="Recycling" ItemsSource="{Binding MoineauPumpFlanks.Stator.Flank.Boundary, Mode=OneWay}" AlternationCount="{Binding MoineauPumpFlanks.Stator.Flank.Boundary.Count, Mode=OneWay}" AutoGenerateColumns="False" HorizontalScrollBarVisibility="Hidden" > <DataGrid.Columns> <DataGridTemplateColumn Header="Id"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Margin="0,0,5,0" TextAlignment="Right" Text="{Binding RelativeSource={ RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}, Path=AlternationIndex}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTemplateColumn > <DataGridTemplateColumn.Header> <StackPanel Orientation="Horizontal"> <TextBlock Text="Point ["/> <Controls:DisplayUnits DisplayUnitsAsAbbreviation="True" DisplayUnitsMode="Length"/> <TextBlock Text="]"/> </StackPanel> </DataGridTemplateColumn.Header> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <Controls:LabelForPoint ShowUnits="False" Point="{Binding}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> 

I am searching for an alternate solution :(

3 Comments

I think it's the virtualization that does that. I wouldn't recommend using AlternationIndex in cases where you have enough items to need virtualization.
Will have to try to see if Virtualization does this. Still it's a nasty cross effect I want nothing to do with. My other answer above details the simple safe solution I have.
FWIW, someone came up with this other approach in response to a similar question.
7

When you use Alternation Count remember that you can also Bind the AlternationCount property to the current count of Items of the collection you are binding to since AlternationCount is a DependencyProperty.

AlternationCount="{Binding Path=OpeningTimes.Count,FallbackValue='100'}" 

Hope it helps.

Comments

1

Yes it is! ItemsControl exposes an ItemContainerGenerator property. The ItemContainerGenerator has methods such as IndexFromContainer which can be used to find the index of a given item. Note that if you bind your ItemsControl to a collection of objects, a container is automatically generated for each. You can find the container for each bound item using the ContainerFromItem method.

5 Comments

Can you access those in XAML?
no, you cannot. if you need to do it without code, why not use the MVVM pattern? create a list of items that contain their index.
Because my CollectionViewSource may reorder or filter that list.
@ColinE: Unfortunately, there doesn't seem to be a good (built-in) way to actually do that, as there is no list item-projection wrapper available in the framework. It does seem to me like it is the best way (thus requiring building one's own such wrapper), though the lack of such a class makes me wonder whether creating "a list of items that contain their index" was meant to be the way to go by the WPF/MVVM designers.
This shows this solution in action: stackoverflow.com/questions/7290147/…
0

A more reliable way is to use a value converter to generate a new collection with an index. With a couple of helpers this is pretty painless. I use ReactiveUI's IEnumerable<T>.CreateDerivedCollection() and a helper class I wrote for other purposes called Indexed.

public struct Indexed<T> { public int Index { get; private set; } public T Value { get; private set; } public Indexed(int index, T value) : this() { Index = index; Value = value; } public override string ToString() { return "(Indexed: " + Index + ", " + Value.ToString () + " )"; } } public class Indexed { public static Indexed<T> Create<T>(int indexed, T value) { return new Indexed<T>(indexed, value); } } 

and the converter

public class IndexedConverter : IValueConverter { public object Convert ( object value , Type targetType , object parameter , CultureInfo culture ) { IEnumerable t = value as IEnumerable; if ( t == null ) { return null; } IEnumerable<object> e = t.Cast<object>(); int i = 0; return e.CreateDerivedCollection<object, Indexed<object>> (o => Indexed.Create(i++, o)); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return null; } } 

and in the XAML I can do

 <DataGrid VirtualizingPanel.VirtualizationMode="Recycling" ItemsSource="{Binding MoineauPumpFlanks.Stator.Flank.Boundary, Mode=OneWay, Converter={StaticResource indexedConverter}}" AutoGenerateColumns="False" HorizontalScrollBarVisibility="Hidden" > <DataGrid.Columns> <DataGridTemplateColumn Header="Id"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <!-- Get the index of Indexed<T> --> <TextBlock Margin="0,0,5,0" TextAlignment="Right" Text="{Binding Path=Index}"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTemplateColumn Header="Point" > <DataGridTemplateColumn.CellTemplate> <DataTemplate> <!-- Get the value of Indexed<T> --> <TextBlock Content="{Binding Value}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> 

4 Comments

Isn't that very bad on the performance-side? I mean, observable collections provide the exact info in their CollectionChanged events about what changes were done to which items, so it is not required to refresh the complete list because the handler knows exactly what has changed. And here we introduce a converter that will, upon any change to the list, provide a whole new list to the GUI that binds to the list property. Or is there any additional magic going on I'm not thinking of now?
No it doesn't generate a complete new list on every change. CreateDerivedCollection creates a new readonly collection. It only updates the parts of the downstream collection that need to be changed based on changes to the upstream collection.
but doesnt this render the indexes invalid if the upstream collection exchanges two instances?
Would like to see a dynamic data of this solution (the IndexedConverter) if possible? :)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.