如何获取当前ItemsControl项的索引?

28

有没有办法在WPF中获取当前ItemsControl项的索引?

例如,我想做这样的事情:

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

这样,完成后第一个 TextBox 将显示文本 "0",第二个将显示 "1",第三个将显示 "2" ...


6个回答

39

我建议查看:

WPF ItemsControl如何在ItemsSource中获取当前ListItem的索引

它解释了如何解决ItemsControl上没有内置Index属性的问题。

编辑:

我尝试了以下代码:

<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 >

并获得一个带有三个TextBlock的窗口,如下所示:

[Index is 0]
[Index is 1]
[Index is 2]

2
对于其他找到这篇文章的人,我喜欢并使用了Saykor的解决方案。我听说不能总是依赖AlternationCount从零开始。这感觉像一个hack。最好使用转换器,并传递所需的信息以确定索引。 - Skystrider
1
@Skychan,你能提供更多的论据吗?为什么这种使用方式的AlternationCount应该是不可靠的? - Leonidas
2
@Leonidas:我发现在计算布局时,AlternationIndex 值经常以零的形式传递到我的代码后台中,因此对于我想要的不仅仅是显示时间绑定,它并没有很好地工作。使用 ItemsSource 中的项进行 ReferenceEquals 来确定它们的索引是一个可靠的替代方法。 - Jason Williams
这种方法根本不可靠,如果启用了虚拟化,情况会更糟...也许它可以用于非常简单的列表,在这种情况下,您可以将AlternationCount绑定到列表中项目的计数,并且可能将Text绑定设置为OneTime,并创建AddOneConverter以从1开始而不是0。 - Mr. Noob
以上评论中提到的AlternationIndex不可靠性可能的参考资料:https://dev59.com/Omw15IYBdhLWcg3wmc4J#17962582 - Ben

10

这里是我获取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>

还有转换器:

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");
    }
}

通过这种方式,您可以将任何类型的集合绑定到ItemSource,并将其更改为ListCollectionView。因此,转换器将适用于不同类型的集合。
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"

这里有一个类似的替代方案,它使用ItemContainerGenerator来确定索引。 https://dev59.com/e1rUa4cB1Zd3GeqPoekX - Skystrider
我认为这种方法不会正确地工作:IndexOf使用Equals,如果源集合有重复项(例如两个相同的字符串值),IndexOf将始终返回第一个项目。 - aderesh
我认为使用ItemContainerGenerator的解决方案更好。谢谢。 - aderesh
@aderesh,你肯定可以只需更改Convert方法以执行任何你想要的操作(例如使用ReferenceEquals)? - Ruben9922
请注意,如果您在某个期望字符串的地方使用转换器(例如 <TextBox.Text>),则需要从 Convert 方法返回一个字符串,可以使用 return itemIndex.ToString() 或者更通用的 return System.Convert.ChangeType(itemIndex, targetType) - Ruben9922

7

看这个

 <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>

转换器长这样。
 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();
    }
}

因此 这里输入图片描述


1
请注意,这仅在视图与原始源完全匹配时才有效,即不进行排序或筛选等操作。 - Peter Duniho

1
如果您的目标是让ItemTemplate中的按钮正常工作,我建议使用DataContext。您还应该能够使用LINQ从DataContext和ItemsSource中找到索引。
如果使用命令
Command="{Binding DataContext.TestCmd, ElementName=Parent_UC}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Mode=Self}}"

如果使用事件,使用发送器。
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
   if(sender is Button b)
   {
      if(b.DataContext is ClassType t)
      { enter code here }
   }
}

0

我是通过计算添加元素的索引来完成的转换器。

它只能单向工作。如果您以某种方式删除了项目或更改了集合,则应使用其他方法。 并且,您需要为每个需要进行索引的元素的集合创建单独的转换器。

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();
    }
}

我在 XAML 中这样使用它

<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>

这个程序会为集合中的每个元素绘制一条线,X坐标偏移量为BaseValue。


-1

有一种方法可以在不使用转换器的情况下实现这一点,并且具有在集合中具有重复项的能力,但这意味着您必须使用 KeyValuePair<int, T> 作为列表项类型来维护组织良好的列表并存储索引。

这里是一个字符串列表的示例实现。它将显示按钮内部的文本,并将索引绑定到命令参数:

#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>
...

在XAML中,Key是索引,Value是实际项,本例中为字符串。此示例未包含Command属性。另请注意,任何更改源列表都将重新创建有序列表,这将触发重新渲染并导致大型列表变慢。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接