Reputation: 50657
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" ...
.
Upvotes: 30
Views: 45200
Reputation: 12579
There is a way to do this without a converter and with the ability to have duplicated items in the collection, but it means that you have to maintain an organized list that also stores the index by using KeyValuePair<int, T>
as list item type.
Here is a sample implementation for a String list. It will show the text inside the buttons and will bind the index on the command parameter:
#region Items
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection<string>), typeof(CulturePicker),
new FrameworkPropertyMetadata(new ObservableCollection<string>(),
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(OnItemsChanged)));
public ObservableCollection<string> Items
{
get { return (ObservableCollection<string>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
private static void OnItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CulturePicker _this = (CulturePicker)d;
ObservableCollection<string> oldItems = (ObservableCollection<string>)e.OldValue;
ObservableCollection<string> newItems = _this.Items;
if (oldItems != null)
{
oldItems.CollectionChanged -= this.Items_CollectionChanged;
}
List<KeyValuePair<int, string>> organizedItems = new List<KeyValuePair<int, string>>();
for (int i = 0; i < newItems.Count; i++)
{
organizedItems.Add(new KeyValuePair<int, string>(i, newItems[i]));
}
this.OrganizedItems = organizedItems;
newItems.CollectionChanged += this.Items_CollectionChanged;
}
private void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
List<KeyValuePair<int, string>> organizedItems = new List<KeyValuePair<int, string>>();
for (int i = 0; i < e.NewItems.Count; i++)
{
organizedItems.Add(new KeyValuePair<int, string>(i, (string)e.NewItems[i]));
}
this.OrganizedItems = organizedItems;
}
#endregion
#region OrganizedItems
/// <summary>
/// OrganizedItems Dependency Property
/// </summary>
private static readonly DependencyProperty OrganizedItemsProperty =
DependencyProperty.Register("OrganizedItems", typeof(List<KeyValuePair<int, string>>), typeof(CulturePicker),
new FrameworkPropertyMetadata((List<KeyValuePair<int, string>>)null,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(OnOrganizedItemsChanged)));
/// <summary>
/// Gets or sets the OrganizedItems property. This dependency property
/// indicates an organized dictionary with the index of the Items as key and the region itself as value.
/// </summary>
private List<KeyValuePair<int, string>> OrganizedItems
{
get { return (List<KeyValuePair<int, string>>)GetValue(OrganizedItemsProperty); }
set { SetValue(OrganizedItemsProperty, value); }
}
/// <summary>
/// Handles changes to the OrganizedItems property.
/// </summary>
private static void OnOrganizedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CulturePicker _this = (CulturePicker)d;
List<KeyValuePair<int, string>> oldOrganizedItems = (List<KeyValuePair<int, string>>)e.OldValue;
List<KeyValuePair<int, string>> newOrganizedItems = _this.OrganizedItems;
}
#endregion
<UserControl ...
Name="_">
...
<ItemsControl ItemsSource="{Binding OrganizedItems, ElementName=_}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding Command, ElementName=_}"
CommandParameter="{Binding Key}"
Text="{Binding Value}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
...
In the XAML Key is the index and Value is the actual item, in this case a the string. The Command
property itself is not included in this example. Also please note that it will re-create the organized list on any change of the source list which will trigger the re-render and cause slowness on big lists.
Upvotes: -1
Reputation: 302
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 }
}
}
Upvotes: 1
Reputation: 315
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.
Upvotes: 0
Reputation: 707
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"
Upvotes: 10
Reputation: 1583
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]
Upvotes: 40
Reputation: 3448
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
Upvotes: 7