【问题标题】:How to get the index of the current ItemsControl item?如何获取当前ItemsControl项的索引?
【发布时间】:2014-03-13 12:19:29
【问题描述】:

有什么方法可以获取ItemsControlWPF中的当前项的索引吗?

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

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

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

【问题讨论】:

标签: c# wpf data-binding itemscontrol


【解决方案1】:

我建议看看:

WPF ItemsControl the current ListItem Index in the ItemsSource

它解释了如何解决 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]

【讨论】:

  • 对于其他发现此问题的人,我喜欢并使用了 Saykor 的解决方案。我听说你不能依赖 AlternationCount 总是从零开始。感觉像一个黑客。而是使用转换器并将确定索引所需的信息传递给它。
  • @Skychan 您能否提供进一步的论据,为什么使用这种方式的 AlternationCount 应该不可靠?
  • @Leonidas:我发现在计算布局时,对于所有项目,AlternationIndex 值经常被传递给我的代码隐藏为零,所以它对我来说效果不佳它不仅仅是显示时间绑定。对 ItemsSource 中的项目使用 ReferenceEquals 来确定它们的索引是一种可靠的替代方法。
  • 这种方法根本不可靠,如果您启用了虚拟化,它会变得更糟......也许它可以用于非常简单的列表,在这种情况下,您可以将 AlternationCount 绑定到列表中的项目,并可能将 Text 绑定设置为 OneTime 并创建 AddOneConverter 以从 1 而不是 0 开始
  • 以上评论的 AlternationIndex 不可靠性的可能参考:stackoverflow.com/a/17962582/7224691
【解决方案2】:

我如何获得 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 来确定索引。 stackoverflow.com/questions/7290147/…
  • 我认为这种方法不能正常工作:IndexOf 使用 Equals 并且如果源集合有重复项(例如两个相同的字符串值),IndexOf 将始终返回第一项。
  • 我认为使用 ItemContainerGenerator 的解决方案更好。谢谢。
  • @aderesh 当然你可以改变 Convert 方法来做任何你喜欢的事情(例如使用 ReferenceEquals 代替)?
  • 请注意,如果您在需要字符串的地方使用转换器(例如&lt;TextBox.Text&gt;),您将需要从Convert 方法返回一个字符串,可以使用return itemIndex.ToString() 或(更多一般)return System.Convert.ChangeType(itemIndex, targetType).
【解决方案3】:

看看这个

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

因此

【讨论】:

  • 请注意,这仅在视图与原始源完全匹配时才有效,即例如未排序或过滤。
【解决方案4】:

如果您的目标是让 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 }
   }
}

【讨论】:

    【解决方案5】:

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

    它只能以一种方式工作。如果您以某种方式删除项目或更改集合,您应该使用其他东西。 你应该为每个集合创建单独的转换器,哪些元素需要被索引。

    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 偏移量。

    【讨论】:

      【解决方案6】:

      有一种方法可以在没有转换器的情况下执行此操作,并且能够在集合中包含重复项,但这意味着您必须维护一个有组织的列表,该列表还使用 KeyValuePair&lt;int, T&gt; 作为列表项类型来存储索引.

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

      #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 中,键是索引,值是实际项目,在本例中是字符串。 Command 属性本身不包含在此示例中。另请注意,它会在源列表发生任何更改时重新创建有组织的列表,这将触发重新渲染并导致大列表运行缓慢。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-24
        • 2019-04-09
        • 1970-01-01
        • 2015-05-12
        相关资源
        最近更新 更多