【问题标题】:WPF ItemsControl the current ListItem Index in the ItemsSourceWPF ItemsControl 在 ItemsSource 中的当前 ListItem 索引
【发布时间】:2011-09-24 13:37:06
【问题描述】:

是否可以知道 ItemsControl 中当前项目的索引?

编辑这行得通!

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

看起来像这样:

【问题讨论】:

  • 一种方法是创建一个自定义的 IValueConverter 并将 items 属性作为参数传递,然后通过查看集合来获取索引,尽管这不是很有效。
  • 这也不可靠。如果它被 CollectionViewSource 过滤或排序怎么办?聪明,我承认,但还有更好的方法。

标签: wpf indexing itemscontrol


【解决方案1】:

是的! ItemsControl 公开了一个 ItemContainerGenerator 属性。 ItemContainerGenerator 具有诸如IndexFromContainer 之类的方法,可用于查找给定项目的索引。请注意,如果您将ItemsControl 绑定到对象集合,则会为每个对象自动生成一个容器。您可以使用ContainerFromItem 方法找到每个绑定项目的容器。

【讨论】:

  • 你能在 XAML 中访问那些吗?
  • 不,你不能。如果你需要在没有代码的情况下完成它,为什么不使用 MVVM 模式呢?创建包含其索引的项目列表。
  • 因为我的 CollectionViewSource 可能会重新排序或过滤该列表。
  • @ColinE:不幸的是,实际上似乎没有一个好的(内置)方法可以做到这一点,因为框架中没有可用的list item-projection wrapper。在我看来,它确实是最好的方法(因此需要构建自己的此类包装器),尽管缺少这样的类让我想知道创建“包含其索引的项目列表”是否意味着由 WPF/MVVM 设计者去。
  • 这显示了这个解决方案的实际效果:stackoverflow.com/questions/7290147/…
【解决方案2】:

我刚才问了同样的问题here

没有内置的 Index 属性,但您可以将 ItemsControl 的 AlternationCount 设置为高于项目数的值,并绑定到 AlternationIndex

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

需要注意的是,如果您的 ListBox 使用虚拟化作为 bradgonesurfing pointed out here,此解决方案可能不起作用。

【讨论】:

  • 干杯。请记住将您的 AlternationCount 设置为高于 ItemsControl 中的预期项目数!
  • @Rachel:如果有人想要基于 1 的索引而不是基于 0 的索引,您会知道解决方案吗?我想我们可以添加一个转换器来增加计数,但只是好奇你是否知道更好的方法。
  • 我不会这样做。有关发生的错误,请参阅我的实验结果。 stackoverflow.com/questions/6511180/…
  • 嗨 Rachel,仅供参考 BradDotNet 制定了一个不使用 AlternationIndex 的不同解决方案。 It's here, if you're curious.
  • 感谢@LynnCrumbling,很高兴知道替代解决方案:)
【解决方案3】:

当您使用交替计数时,请记住您还可以将AlternationCount 属性绑定到您要绑定的集合的当前项计数,因为AlternationCountDependencyProperty

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

希望对你有帮助。

【讨论】:

    【解决方案4】:

    这不是一个答案,而是一个建议。不要按照建议使用 AlternationIndex 技术。它似乎首先起作用,但有一些奇怪的副作用。看来您不能保证 AlternationIndex 从 0 开始。

    在第一次渲染时它可以正常工作

    但重新调整 Grid 的大小,然后在索引中展开结果 不再从零开始。您可以在下面看到效果 图片

    这是从以下 XAML 生成的。里面有一些自定义组件,但你会明白的。

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

    我正在寻找替代解决方案:(

    【讨论】:

    • 我认为是虚拟化做到了这一点。如果您有足够的项目需要虚拟化,我不建议使用 AlternationIndex
    • 将不得不尝试看看虚拟化是否这样做。仍然是一个令人讨厌的交叉效应,我不想与之有任何关系。我上面的其他答案详细说明了我拥有的简单安全解决方案。
    • FWIW,有人想出了this other approach 来回答类似的问题。
    【解决方案5】:

    更可靠的方法是使用值转换器生成带有索引的新集合。有几个助手,这很轻松。我使用 ReactiveUI's IEnumerable&lt;T&gt;.CreateDerivedCollection() 和一个我为其他目的编写的名为 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);
        }
    }
    

    和转换器

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

    在 XAML 中我可以做到

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

    【讨论】:

    • 在性能方面不是很糟糕吗?我的意思是,可观察集合在其CollectionChanged 事件中提供了有关对哪些项目进行了哪些更改的确切信息,因此不需要刷新完整列表,因为处理程序确切地知道发生了什么更改。在这里,我们介绍了一个转换器,该转换器将在列表发生任何更改时,为绑定到列表属性的 GUI 提供一个全新的列表。或者还有什么我现在没有想到的额外魔法?
    • 不,它不会在每次更改时生成完整的新列表。 CreateDerivedCollection 创建一个新的只读集合。它仅根据上游集合的更改更新下游集合中需要更改的部分。
    • 但是如果上游集合交换两个实例,这不会导致索引无效吗?
    猜你喜欢
    • 2010-12-21
    • 1970-01-01
    • 2017-01-21
    • 1970-01-01
    • 2010-11-29
    • 2011-03-27
    • 1970-01-01
    • 1970-01-01
    • 2011-11-27
    相关资源
    最近更新 更多